diff --git a/src/base/base_crum.c b/src/base/base_crum.c index f80a6db3..bc5b4436 100644 --- a/src/base/base_crum.c +++ b/src/base/base_crum.c @@ -1,6 +1,47 @@ //////////////////////////////////////////////////////////// //~ Value conversion utilities +String CR_SanitizeString(Arena *arena, String str) +{ + String result = Zi; + result.text = ArenaNext(arena, u8); + for (u64 idx = 0; idx < str.len; ++idx) + { + u8 c = str.text[idx]; + String sanitized = Zi; + switch (c) + { + default: + { + sanitized.text = &c; + sanitized.len = 1; + } break; + + case '\r': + { + // Ignore + } break; + + case '\n': + { + sanitized = Lit("\\n"); + } break; + + case '\\': + { + sanitized = Lit("\\"); + } break; + + case '"': + { + sanitized = Lit("\\\""); + } break; + } + result.len += PushString(arena, sanitized).len; + } + return result; +} + i64 CR_IntFromString(String str) { i64 result = 0; @@ -330,7 +371,7 @@ CR_Item *CR_ItemFromString(Arena *arena, String str) } else if (c == '\"') { - if (pos == text_start) + if (!text_is_string && pos == text_start) { text_is_string = 1; text_start = pos + 1; diff --git a/src/base/base_crum.h b/src/base/base_crum.h index 43608015..a77e0ba6 100644 --- a/src/base/base_crum.h +++ b/src/base/base_crum.h @@ -17,6 +17,7 @@ Struct(CR_Item) //////////////////////////////////////////////////////////// //~ Value conversion utilities +String CR_SanitizeString(Arena *arena, String str); i64 CR_IntFromString(String str); f64 CR_FloatFromString(String str); b32 CR_BoolFromString(String str); diff --git a/src/pp/pp.c b/src/pp/pp.c index 41ebb126..008ea5a7 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -210,166 +210,26 @@ P_Anim P_AnimFromEnt(P_Ent *ent, i64 time_ns) { P_Anim result = Zi; - i64 animation_rate_ns = NsFromSeconds(0.100); - // TODO: Determine animation dynamically + i64 animation_rate_ns = NsFromSeconds(0.100); { i64 walk_duration_ns = time_ns; result.frame_seq = walk_duration_ns / animation_rate_ns; result.span = SPR_SpanKeyFromName(Lit("walk")); } - result.sheet = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/bla3.ase"))); - result.wep_sheet = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/raah.ase"))); + // TODO: Use prefab lookup + if (ent->is_guy) + { + result.sheet = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/bla3.ase"))); + result.wep_sheet = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/raah.ase"))); + } + else if (ent->is_guy_spawn) + { + result.sheet = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("prefab/GuySpawn.ase"))); + } return result; - - - - - - - - - - - - - // P_Anim result = Zi; - - // // result.slice_to_world_af = AffineIdentity; - // // result.slice_to_world_af = ent->af; - - // i64 animation_rate_ns = NsFromSeconds(0.100); - - // SPR_SpanKey span_key = Zi; - // { - // // TODO: Determine span dynamically - // span_key = SPR_SpanKeyFromName(Lit("walk")); - // } - - // SPR_SheetKey body_sheet_key = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/bla3.ase"))); - // SPR_SheetKey weapon_sheet_key = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/raahase"))); - - // //- Body - // { - // // TODO: Real walk animation - // i64 walk_duration_ns = time_ns; - // i64 frame_seq = walk_duration_ns / animation_rate_ns; - - // SPR_Frame sframe = SPR_FrameFromSheet(body_sheet_key, span_key, frame_seq); - - // // FIXME: Use origin ray - // { - // Vec2 world_dims = DivVec2(sframe.dims, P_CellsPerMeter); - // result.body_to_world_af = AffineIdentity; - // result.body_to_world_af.og = ent->af.og; - // // result.body_to_world_af.og = SubVec2(result.body_to_world_af.og, MulVec2(world_dims, 0.25)); - // result.body_to_world_af = AffineWithWorldRotation(result.body_to_world_af, AngleFromVec2(ent->control.look)); - // result.body_to_world_af = TranslateAffine(result.body_to_world_af, MulVec2(world_dims, -0.5)); - // result.body_to_world_af = ScaleAffine(result.body_to_world_af, world_dims); - // } - - // { - // result. - // } - - - // result.body_slice = sframe.slice; - - // { - // // result. - // } - - - - // // SPR_Ray origin_ray = SPR_RayFromFrame(body_sheet_key, origin_layer_key, frame_idx); - // // SPR_Ray ap_ray = SPR_RayFromFrame(body_sheet_key, ap_layer_key, frame_idx); - - // // { - // // // Vec2 world_dims = VEC2(0.5, 0.5); - // // Vec2 px_dims = slice.dims; - // // Vec2 world_dims = DivVec2(px_dims, P_CellsPerMeter); - // // result.slice_to_world_af = AffineIdentity; - // // result.slice_to_world_af.og = ent->af.og; - // // // result.slice_to_world_af.og = SubVec2(result.slice_to_world_af.og, MulVec2(world_dims, 0.25)); - // // result.slice_to_world_af = AffineWithWorldRotation(result.slice_to_world_af, AngleFromVec2(ent->control.look)); - // // result.slice_to_world_af = TranslateAffine(result.slice_to_world_af, MulVec2(world_dims, -0.5)); - // // result.slice_to_world_af = ScaleAffine(result.slice_to_world_af, world_dims); - // // } - - // // result.slice = SPR_SliceFromSheet(sheet, - - // } - - // //- Weapon - // { - // } - - // return result; - - - - - - - - - - // P_Anim result = Zi; - - // // result.slice_to_world_af = AffineIdentity; - // // result.slice_to_world_af = ent->af; - - // i64 animation_rate_ns = NsFromSeconds(0.100); - - // SPR_LayerKey origin_layer_key = SPR_LayerKeyFromName(Lit(".origin")); - // SPR_LayerKey ap_layer_key = SPR_LayerKeyFromName(Lit(".ap")); - // SPR_SpanKey span_key = Zi; - // { - // // TODO: Determine span dynamically - // span_key = SPR_SpanKeyFromName(Lit("walk")); - // } - - // SPR_SheetKey body_sheet_key = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/bla3.ase"))); - // SPR_SheetKey weapon_sheet_key = SPR_SheetKeyFromResource(ResourceKeyFromStore(&P_Resources, Lit("sprite/raahase"))); - - // // Body - // { - // i64 frame_idx = 0; - // { - // // TODO: Real walk animation - // SPR_Span span = SPR_SpanFromKey(body_sheet_key, span_key); - // i64 walk_duration_ns = time_ns; - // i64 frame_seq = walk_duration_ns / animation_rate_ns; - // i64 frame_idx = span.start + (frame_seq % (span.end - span.start)); - // } - - // SPR_Slice slice = SPR_SliceFromFrame(body_sheet_key, SPR_NilLayerKey, frame_idx); - // SPR_Ray origin_ray = SPR_RayFromFrame(body_sheet_key, origin_layer_key, frame_idx); - // SPR_Ray ap_ray = SPR_RayFromFrame(body_sheet_key, ap_layer_key, frame_idx); - - // { - // // Vec2 world_dims = VEC2(0.5, 0.5); - // Vec2 px_dims = slice.dims; - // Vec2 world_dims = DivVec2(px_dims, P_CellsPerMeter); - // result.slice_to_world_af = AffineIdentity; - // result.slice_to_world_af.og = ent->af.og; - // // result.slice_to_world_af.og = SubVec2(result.slice_to_world_af.og, MulVec2(world_dims, 0.25)); - // result.slice_to_world_af = AffineWithWorldRotation(result.slice_to_world_af, AngleFromVec2(ent->control.look)); - // result.slice_to_world_af = TranslateAffine(result.slice_to_world_af, MulVec2(world_dims, -0.5)); - // result.slice_to_world_af = ScaleAffine(result.slice_to_world_af, world_dims); - // } - - // // result.slice = SPR_SliceFromSheet(sheet, - - // } - - // // Weapon - // { - // } - - // return result; } //////////////////////////////////////////////////////////// diff --git a/src/pp/pp.h b/src/pp/pp.h index fa940ee0..dcbd6c35 100644 --- a/src/pp/pp.h +++ b/src/pp/pp.h @@ -68,8 +68,7 @@ Struct(P_DebugDrawNode) //~ Ent types // TODO: Move boolean fields into bitwise property flags - -// TODO: Pack efficiently, deduplicate redundant fields +// TODO: Pack efficiently #define P_MinPlayerNameLen 1 #define P_MaxPlayerNameLen 24 @@ -140,6 +139,10 @@ Struct(P_Ent) u8 string_len; u8 string_text[P_MaxPlayerNameLen + 8]; + //- Spawn + + b32 is_guy_spawn; + //- Solver Vec2 solved_v; diff --git a/src/pp/pp_res/prefab/GuySpawn.ase b/src/pp/pp_res/prefab/GuySpawn.ase new file mode 100644 index 00000000..ecd9b2aa --- /dev/null +++ b/src/pp/pp_res/prefab/GuySpawn.ase @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc0ee0ff942f817e4e412a038f261982be795a11cc0850abae06429179576f6e +size 559 diff --git a/src/pp/pp_shared.cgh b/src/pp/pp_shared.cgh index d957297f..bab2fb02 100644 --- a/src/pp/pp_shared.cgh +++ b/src/pp/pp_shared.cgh @@ -37,7 +37,7 @@ Enum(P_TileKind) X(None, P_PrefabFlag_HideFromEditor) \ X(Guy, P_PrefabFlag_None) \ X(Dummy, P_PrefabFlag_None) \ - X(SpawnPoint, P_PrefabFlag_None) \ + X(GuySpawn, P_PrefabFlag_None) \ /* --------------------------------------------------- */ //- Prefab flags diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index 780ef9b0..c5c3ecc2 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -232,6 +232,10 @@ void S_TickForever(WaveLaneCtx *lane) DllQueuePushNP(bin->first, bin->last, client, next_in_bin, prev_in_bin); } + // FIXME: Only accept desired player key from host client + client->player = msg->key; + + // Acknowledge player connection { @@ -292,7 +296,7 @@ void S_TickForever(WaveLaneCtx *lane) } ////////////////////////////// - //- Create guy entities + //- Spawn guys { P_EntList new_guys = Zi; @@ -308,10 +312,68 @@ void S_TickForever(WaveLaneCtx *lane) if (P_IsEntNil(guy)) { guy = P_PushTempEnt(frame_arena, &new_guys); + guy->is_guy = 1; + guy->has_weapon = 1; guy->key = player->guy; + + //- Choose guy spawn point + { + P_Ent *highest_scoring_spawn = &P_NilEnt; + { + Struct(SpawnNode) + { + SpawnNode *next; + f32 score; + P_Ent *ent; + }; + SpawnNode *first_spawn_node = 0; + SpawnNode *last_spawn_node = 0; + + // Push spawns + for (P_Ent *spawn = P_FirstEnt(world_frame); !P_IsEntNil(spawn); spawn = P_NextEnt(spawn)) + { + if (spawn->is_guy_spawn) + { + SpawnNode *spawn_node = PushStruct(frame_arena, SpawnNode); + SllQueuePush(first_spawn_node, last_spawn_node, spawn_node); + spawn_node->ent = spawn; + spawn_node->score = Inf; + } + } + + // Score spawns + for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + b32 should_score = 0; + if (ent->is_guy) + { + should_score = 1; + } + if (should_score) + { + for (SpawnNode *spawn_node = first_spawn_node; spawn_node; spawn_node = spawn_node->next) + { + f32 score = Vec2Len(SubVec2(ent->xf.t, spawn_node->ent->xf.t)); + spawn_node->score = MinF32(spawn_node->score, score); + } + } + } + + // Find highest scoring spawn_node + i64 highest_score = -Inf; + for (SpawnNode *spawn_node = first_spawn_node; spawn_node; spawn_node = spawn_node->next) + { + if (spawn_node->score > highest_score) + { + highest_score = spawn_node->score; + highest_scoring_spawn = spawn_node->ent; + } + } + } + + guy->xf = highest_scoring_spawn->xf; + } } - guy->is_guy = 1; - guy->has_weapon = 1; } } P_SpawnEntsFromList(world_frame, new_guys); @@ -592,33 +654,42 @@ void S_TickForever(WaveLaneCtx *lane) guy->xf = msg->xf; guy->is_guy = 1; guy->has_weapon = 1; - guy->exists = 1; } break; case P_PrefabKind_Dummy: { P_Ent *dummy = P_EntFromKey(world_frame, msg->key); - if (P_IsEntNil(dummy)) + if (!dummy->is_dummy) { dummy = P_PushTempEnt(frame_arena, &ents); dummy->key = msg->key; dummy->is_player = 1; dummy->is_dummy = 1; - dummy->exists = 1; P_SetEntString(dummy, name); } P_Ent *guy = P_EntFromKey(world_frame, dummy->guy); - if (P_IsEntNil(guy)) + if (!guy->is_guy) { guy = P_PushTempEnt(frame_arena, &ents); guy->key = P_RandKey(); guy->is_guy = 1; guy->has_weapon = 1; - guy->exists = 1; dummy->guy = guy->key; } guy->xf = msg->xf; } break; + + case P_PrefabKind_GuySpawn: + { + P_Ent *spawn = P_EntFromKey(world_frame, msg->key); + if (!spawn->is_guy_spawn) + { + spawn = P_PushTempEnt(frame_arena, &ents); + spawn->key = msg->key; + spawn->is_guy_spawn = 1; + } + spawn->xf = msg->xf; + } break; } P_SpawnEntsFromList(world_frame, ents); } break; diff --git a/src/pp/pp_transcode.c b/src/pp/pp_transcode.c index f1428060..30fb92a2 100644 --- a/src/pp/pp_transcode.c +++ b/src/pp/pp_transcode.c @@ -26,10 +26,6 @@ String P_PackWorld(Arena *arena, P_World *src_world) { should_pack = 0; } - if (ent->is_player) - { - should_pack = 0; - } if (should_pack) { result.len += StringF(arena, " 0x%F:\n", FmtHex(ent->key.v)).len; @@ -58,13 +54,20 @@ String P_PackWorld(Arena *arena, P_World *src_world) { result.len += PushString(arena, Lit(" bullet\n")).len; } + if (ent->is_guy_spawn) + { + result.len += PushString(arena, Lit(" guy_spawn\n")).len; + } } result.len += StringF(arena, " }\n").len; result.len += StringF(arena, " pos: \"%F\"\n", FmtFloat2(ent->xf.t)).len; result.len += StringF(arena, " angle: \"%F\"\n", FmtFloat(AngleFromVec2(ent->xf.r))).len; + result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->control.look)).len; result.len += StringF(arena, " exists: \"%F\"\n", FmtFloat(ent->exists)).len; result.len += StringF(arena, " look: \"%F\"\n", FmtFloat2(ent->control.look)).len; result.len += StringF(arena, " health: \"%F\"\n", FmtFloat(ent->health)).len; + result.len += StringF(arena, " guy: \"0x%F\"\n", FmtHex(ent->guy.v)).len; + result.len += StringF(arena, " text: \"%F\"\n", FmtString(CR_SanitizeString(scratch.arena, STRING(ent->string_len, ent->string_text)))).len; } result.len += PushString(arena, Lit(" }\n")).len; } @@ -159,6 +162,10 @@ P_UnpackedWorld P_UnpackWorld(Arena *arena, String packed) { ent->is_bullet = 1; } + if (MatchString(prop->value, Lit("guy_spawn"))) + { + ent->is_guy_spawn = 1; + } } } if (MatchString(attr->name, Lit("pos"))) @@ -169,21 +176,29 @@ P_UnpackedWorld P_UnpackWorld(Arena *arena, String packed) { ent->xf.r = Vec2FromAngle(CR_FloatFromString(attr->value)); } + if (MatchString(attr->name, Lit("look"))) + { + ent->control.look = CR_Vec2FromString(attr->value); + } if (MatchString(attr->name, Lit("exists"))) { ent->exists = CR_FloatFromString(attr->value); } - if (MatchString(attr->name, Lit("health"))) - { - ent->health = CR_FloatFromString(attr->value); - } if (MatchString(attr->name, Lit("look"))) { ent->control.look = CR_Vec2FromString(attr->value); } if (MatchString(attr->name, Lit("health"))) { - ent->health = CR_FloatFromString(attr->value);; + ent->health = CR_FloatFromString(attr->value); + } + if (MatchString(attr->name, Lit("guy"))) + { + ent->guy.v = CR_IntFromString(attr->value); + } + if (MatchString(attr->name, Lit("text"))) + { + P_SetEntString(ent, attr->value); } } } diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 5a7967ca..f66b416d 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -630,6 +630,8 @@ void V_TickForever(WaveLaneCtx *lane) BB_WriteF32(&bbw, prev_frame->edit_camera_pos.x); BB_WriteF32(&bbw, prev_frame->edit_camera_pos.y); BB_WriteF32(&bbw, prev_frame->edit_camera_zoom); + BB_WriteF32(&bbw, prev_frame->look.x); + BB_WriteF32(&bbw, prev_frame->look.y); BB_WriteString(&bbw, prev_frame->window_restore); } else @@ -639,6 +641,8 @@ void V_TickForever(WaveLaneCtx *lane) frame->edit_camera_pos.x = BB_ReadF32(&bbr); frame->edit_camera_pos.y = BB_ReadF32(&bbr); frame->edit_camera_zoom = BB_ReadF32(&bbr); + frame->look.x = BB_ReadF32(&bbr); + frame->look.y = BB_ReadF32(&bbr); frame->window_restore = BB_ReadString(frame->arena, &bbr); } @@ -1101,7 +1105,8 @@ void V_TickForever(WaveLaneCtx *lane) ////////////////////////////// //- Try to connect to server - frame->desired_sim_key = NET_KeyFromString(Lit("127.0.0.1"), Lit("22121")); + NET_Key local_sim_key = NET_KeyFromString(Lit("127.0.0.1"), Lit("22121")); + frame->desired_sim_key = local_sim_key; if (!NET_MatchKey(frame->sim_key, frame->desired_sim_key)) { i64 retry_rate_ns = NsFromSeconds(0.100); @@ -1122,6 +1127,7 @@ void V_TickForever(WaveLaneCtx *lane) P_MsgNode tmp_msg_node = Zi; tmp_msg_node.msg.kind = P_MsgKind_Connect; tmp_msg_node.msg.data = Lit("Bro"); + tmp_msg_node.msg.key = V.player_key; P_MsgList tmp_msglist = Zi; tmp_msglist.count = 1; @@ -2070,52 +2076,90 @@ void V_TickForever(WaveLaneCtx *lane) - - - ////////////////////////////// - //- Draw guy sprites + //- Draw entities - for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - if (ent->is_guy) + Struct(DrawNode) { - P_Anim anim = P_AnimFromEnt(ent, local_frame->time_ns); - SPR_Sprite body = SPR_SpriteFromSheet(anim.sheet, anim.span, anim.frame_seq); - SPR_Sprite wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq); + DrawNode *next; + P_Ent *ent; + }; - //- Compute sprite transforms - Affine body_pix_to_world_af = AffineIdentity; - Affine wep_pix_to_world_af = AffineIdentity; + i64 draw_nodes_count = 0; + DrawNode *first_draw_node = 0; + DrawNode *last_draw_node = 0; + + //- Push editor ents + if (frame->is_editing) + { + for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter); - Affine ent_to_world_af = MulAffineXform(AffineIdentity, ent->xf); - - //- Compute body transform + // TODO: Use entity's prefab flags to determine editor draw status + if (ent->is_guy_spawn) { - body_pix_to_world_af = MulAffine(body_pix_to_world_af, ent_to_world_af); - body_pix_to_world_af = ScaleAffine(body_pix_to_world_af, pix_scale); - - SPR_Ray anchor_ray = body.rays[SPR_RayKind_Anchor]; - body_pix_to_world_af = RotateAffine(body_pix_to_world_af, InvertRot(anchor_ray.dir)); - body_pix_to_world_af = TranslateAffine(body_pix_to_world_af, NegVec2(anchor_ray.pos)); + DrawNode *draw_node = PushStruct(frame->arena, DrawNode); + SllQueuePush(first_draw_node, last_draw_node, draw_node); + ++draw_nodes_count; + draw_node->ent = ent; } + } + } - //- Compute weapon transform - { - wep_pix_to_world_af = MulAffine(wep_pix_to_world_af, ent_to_world_af); - wep_pix_to_world_af = ScaleAffine(wep_pix_to_world_af, pix_scale); + //- Push guys + for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + if (ent->is_guy) + { + DrawNode *draw_node = PushStruct(frame->arena, DrawNode); + SllQueuePush(first_draw_node, last_draw_node, draw_node); + ++draw_nodes_count; + draw_node->ent = ent; + } + } - SPR_Ray body_anchor_ray = body.rays[SPR_RayKind_Anchor]; - SPR_Ray body_ap_ray = body.rays[SPR_RayKind_Ap]; - wep_pix_to_world_af = RotateAffine(wep_pix_to_world_af, InvertRot(body_anchor_ray.dir)); - wep_pix_to_world_af = TranslateAffine(wep_pix_to_world_af, SubVec2(body_ap_ray.pos, body_anchor_ray.pos)); - wep_pix_to_world_af = RotateAffine(wep_pix_to_world_af, InvertRot(body_ap_ray.dir)); + //- Build quads + for (DrawNode *draw_node = first_draw_node; draw_node; draw_node = draw_node->next) + { + P_Ent *ent = draw_node->ent; + P_Anim anim = P_AnimFromEnt(ent, local_frame->time_ns); - SPR_Ray anchor_ray = wep.rays[SPR_RayKind_Anchor]; - wep_pix_to_world_af = RotateAffine(wep_pix_to_world_af, anchor_ray.dir); - wep_pix_to_world_af = TranslateAffine(wep_pix_to_world_af, NegVec2(anchor_ray.pos)); - } + Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter); + Affine ent_to_world_af = MulAffineXform(AffineIdentity, ent->xf); + + //- Compute body sprite + SPR_Sprite body = Zi; + Affine body_pix_to_world_af = AffineIdentity; + { + body = SPR_SpriteFromSheet(anim.sheet, anim.span, anim.frame_seq); + + body_pix_to_world_af = MulAffine(body_pix_to_world_af, ent_to_world_af); + body_pix_to_world_af = ScaleAffine(body_pix_to_world_af, pix_scale); + + SPR_Ray anchor_ray = body.rays[SPR_RayKind_Anchor]; + body_pix_to_world_af = RotateAffine(body_pix_to_world_af, InvertRot(anchor_ray.dir)); + body_pix_to_world_af = TranslateAffine(body_pix_to_world_af, NegVec2(anchor_ray.pos)); + } + + //- Compute weapon sprite + SPR_Sprite wep = Zi; + Affine wep_pix_to_world_af = AffineIdentity; + if (ent->is_guy) + { + wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq); + + wep_pix_to_world_af = MulAffine(wep_pix_to_world_af, ent_to_world_af); + wep_pix_to_world_af = ScaleAffine(wep_pix_to_world_af, pix_scale); + + SPR_Ray body_anchor_ray = body.rays[SPR_RayKind_Anchor]; + SPR_Ray body_ap_ray = body.rays[SPR_RayKind_Ap]; + wep_pix_to_world_af = RotateAffine(wep_pix_to_world_af, InvertRot(body_anchor_ray.dir)); + wep_pix_to_world_af = TranslateAffine(wep_pix_to_world_af, SubVec2(body_ap_ray.pos, body_anchor_ray.pos)); + wep_pix_to_world_af = RotateAffine(wep_pix_to_world_af, InvertRot(body_ap_ray.dir)); + + SPR_Ray anchor_ray = wep.rays[SPR_RayKind_Anchor]; + wep_pix_to_world_af = RotateAffine(wep_pix_to_world_af, anchor_ray.dir); + wep_pix_to_world_af = TranslateAffine(wep_pix_to_world_af, NegVec2(anchor_ray.pos)); } //- Push weapon quad @@ -2146,7 +2190,6 @@ void V_TickForever(WaveLaneCtx *lane) - ////////////////////////////// //- Push test bullet particles