working spawn points

This commit is contained in:
jacob 2026-02-05 23:51:04 -06:00
parent e0356f73e8
commit e88c91b1ab
9 changed files with 246 additions and 209 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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"));
}
// 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;
}
////////////////////////////////////////////////////////////

View File

@ -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;

BIN
src/pp/pp_res/prefab/GuySpawn.ase (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

View File

@ -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->key = player->guy;
}
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;
}
}
}
}
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;

View File

@ -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);
}
}
}

View File

@ -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,29 +2076,63 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Draw guy sprites
//- Draw entities
{
Struct(DrawNode)
{
DrawNode *next;
P_Ent *ent;
};
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))
{
// TODO: Use entity's prefab flags to determine editor draw status
if (ent->is_guy_spawn)
{
DrawNode *draw_node = PushStruct(frame->arena, DrawNode);
SllQueuePush(first_draw_node, last_draw_node, draw_node);
++draw_nodes_count;
draw_node->ent = ent;
}
}
}
//- Push guys
for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
{
if (ent->is_guy)
{
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 *draw_node = PushStruct(frame->arena, DrawNode);
SllQueuePush(first_draw_node, last_draw_node, draw_node);
++draw_nodes_count;
draw_node->ent = ent;
}
}
//- Compute sprite transforms
Affine body_pix_to_world_af = AffineIdentity;
Affine wep_pix_to_world_af = AffineIdentity;
//- 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);
Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter);
Affine ent_to_world_af = MulAffineXform(AffineIdentity, ent->xf);
//- Compute body transform
//- 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);
@ -2101,8 +2141,13 @@ void V_TickForever(WaveLaneCtx *lane)
body_pix_to_world_af = TranslateAffine(body_pix_to_world_af, NegVec2(anchor_ray.pos));
}
//- Compute weapon transform
//- 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);
@ -2116,7 +2161,6 @@ void V_TickForever(WaveLaneCtx *lane)
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
if (body.ready && wep.ready)
@ -2146,7 +2190,6 @@ void V_TickForever(WaveLaneCtx *lane)
//////////////////////////////
//- Push test bullet particles