distinct bullet hit entities

This commit is contained in:
jacob 2026-03-19 19:49:14 -05:00
parent a76cfc5dfd
commit 18ad1fb69e
6 changed files with 635 additions and 411 deletions

View File

@ -145,6 +145,17 @@ f64 CR_FloatFromString(String str)
} }
} }
String whole_str = STRING(str.len - whole_start_idx, &str.text[whole_start_idx]);
if (MatchString(whole_str, Lit("inf")))
{
result = Inf * sign;
}
else if (MatchString(whole_str, Lit("NaN")))
{
result = Nan;
}
else
{
// Find decimal place // Find decimal place
u64 frac_start_idx = whole_start_idx; u64 frac_start_idx = whole_start_idx;
for (; ok && frac_start_idx < str.len; ++frac_start_idx) for (; ok && frac_start_idx < str.len; ++frac_start_idx)
@ -203,7 +214,7 @@ f64 CR_FloatFromString(String str)
{ {
result = 0; result = 0;
} }
}
return result; return result;
} }

View File

@ -3184,6 +3184,8 @@ void P_StepFrame(P_Frame *frame)
// TODO: Separate 'hits' from bullets, so that bullets can have multiple hits // TODO: Separate 'hits' from bullets, so that bullets can have multiple hits
{
P_EntList hits_to_spawn = Zi;
for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
{ {
if (bullet->is_bullet) if (bullet->is_bullet)
@ -3359,9 +3361,9 @@ void P_StepFrame(P_Frame *frame)
P_EntKey victim_key = Zi; P_EntKey victim_key = Zi;
P_RaycastResult victim_raycast = Zi; P_RaycastResult victim_raycast = Zi;
b32 hit = 0; b32 has_hit = 0;
{ {
for (BulletPath *path = first_bullet_path; path && !hit; path = path->next) for (BulletPath *path = first_bullet_path; path && !has_hit; path = path->next)
{ {
Vec2 path_dir = SubVec2(path->end, path->start); Vec2 path_dir = SubVec2(path->end, path->start);
P_Space *cast_spaces[] = { P_Space *cast_spaces[] = {
@ -3406,7 +3408,7 @@ void P_StepFrame(P_Frame *frame)
closest_len_sq = len_sq; closest_len_sq = len_sq;
victim_key = potential_victim_key; victim_key = potential_victim_key;
victim_raycast = entrance_raycast; victim_raycast = entrance_raycast;
hit = 1; has_hit = 1;
} }
} }
} }
@ -3416,28 +3418,30 @@ void P_StepFrame(P_Frame *frame)
} }
P_Ent *victim = P_EntFromKey(frame, victim_key); P_Ent *victim = P_EntFromKey(frame, victim_key);
bullet->has_hit = 0; // Create hit
Vec2 final_pos = p1; Vec2 final_pos = p1;
if (hit) if (has_hit)
{ {
Vec2 normal = victim_raycast.normal; Vec2 normal = victim_raycast.normal;
bullet->bullet_hits_count += 1;
bullet->has_hit = 1; P_Ent *hit = P_PushTempEnt(scratch.arena, &hits_to_spawn);
bullet->hit_entry = victim_raycast.p; {
bullet->hit_entry_normal = normal; hit->key = P_EntKeyFromU64(MixU64s(bullet->key.v, P_BulletHitBasis + bullet->bullet_hits_count));
hit->is_hit = 1;
hit->hit_entry = victim_raycast.p;
hit->hit_entry_normal = normal;
hit->lifetime_seconds = P_ObservationDurationSeconds;
// FIXME: Use relative velocity at collision point // FIXME: Use relative velocity at collision point
bullet->hit_entry_velocity = bullet->v; hit->hit_entry_velocity = bullet->v;
// bullet->bullet_end = bullet->hit_entry;
if (victim->is_guy) if (victim->is_guy)
{ {
bullet->hit_material = P_MaterialKind_Flesh; hit->hit_material = P_MaterialKind_Flesh;
} }
else else
{ {
bullet->hit_material = P_MaterialKind_Wall; hit->hit_material = P_MaterialKind_Wall;
}
} }
// Reflect velocity along normal // Reflect velocity along normal
@ -3452,7 +3456,7 @@ void P_StepFrame(P_Frame *frame)
} }
f32 collision_offset = 0.01; // Tiny offset along normal to prevent collision with the victim during ricochets f32 collision_offset = 0.01; // Tiny offset along normal to prevent collision with the victim during ricochets
final_pos = AddVec2(bullet->hit_entry, MulVec2(bullet->hit_entry_normal, collision_offset)); final_pos = AddVec2(hit->hit_entry, MulVec2(hit->hit_entry_normal, collision_offset));
} }
bullet->xf.t = final_pos; bullet->xf.t = final_pos;
@ -3485,6 +3489,8 @@ void P_StepFrame(P_Frame *frame)
} }
} }
} }
P_SpawnEntsFromList(frame, hits_to_spawn);
}

View File

@ -8,6 +8,7 @@
#define P_WallShapeIDBasis 0x40d501b4cf6d4f0cull #define P_WallShapeIDBasis 0x40d501b4cf6d4f0cull
#define P_BulletSpreadBasis 0xc3b72fe38ca5a1d6ull #define P_BulletSpreadBasis 0xc3b72fe38ca5a1d6ull
#define P_BulletHitBasis 0xbc70fc783c1c507full
Struct(P_EntKey) Struct(P_EntKey)
{ {
@ -84,7 +85,7 @@ Struct(P_DebugDrawNode)
#define P_RollTimeNs NsFromSeconds(0.5) #define P_RollTimeNs NsFromSeconds(0.5)
#define P_RollTurnTimeNs (NsFromSeconds(0.1)) #define P_RollTurnTimeNs (NsFromSeconds(0.1))
#define P_RollTimeoutNs NsFromSeconds(0.5) #define P_RollTimeoutNs NsFromSeconds(0.5)
#define P_ObservationLifetimeNs NsFromSeconds(2) #define P_ObservationDurationSeconds 1
Struct(P_Control) Struct(P_Control)
{ {
@ -121,8 +122,9 @@ Struct(P_Ent)
//- Client data //- Client data
i64 observation_time_ns; i64 initial_observation_time_ns;
b32 has_observed; i64 last_observation_time_ns;
b32 is_first_observation;
//- Build data //- Build data
@ -137,11 +139,14 @@ Struct(P_Ent)
Xform xf; Xform xf;
//- Bullet / hit //- Bullet
b32 is_bullet; b32 is_bullet;
u32 bullet_hits_count;
b32 has_hit; //- Hit
b32 is_hit;
Vec2 hit_entry; Vec2 hit_entry;
Vec2 hit_entry_normal; Vec2 hit_entry_normal;
Vec2 hit_entry_velocity; Vec2 hit_entry_velocity;

View File

@ -23,7 +23,7 @@ String P_PackWorld(Arena *arena, P_World *src_world)
{ {
// TODO: Pack ignored ents // TODO: Pack ignored ents
b32 ignore = 0; b32 ignore = 0;
if (ent->is_weapon || ent->is_bullet || ent->is_bomb) if (ent->is_weapon || ent->is_bullet || ent->is_bomb || ent->is_hit)
{ {
ignore = 1; ignore = 1;
} }

View File

@ -1895,14 +1895,16 @@ void V_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Observe entities //- Observe entities
for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
{ {
P_EntKey key = ent->key; P_EntKey key = ent->key;
i64 expiration_ns = P_ObservationLifetimeNs; i64 expiration_ns = NsFromSeconds(P_ObservationDurationSeconds);
i64 observation_time_ns = 0; V_Observation *obs = 0;
{ {
V_ObservationBin *bin = &V.observation_bins[key.v % countof(V.observation_bins)]; V_ObservationBin *bin = &V.observation_bins[key.v % countof(V.observation_bins)];
V_Observation *obs = bin->first; obs = bin->first;
for (; obs; obs = obs->next) for (; obs; obs = obs->next)
{ {
if (obs->key == key.v) if (obs->key == key.v)
@ -1910,19 +1912,20 @@ void V_TickForever(WaveLaneCtx *lane)
break; break;
} }
} }
if (obs) if (obs)
{ {
if (obs->time_ns + expiration_ns > frame->time_ns) if (frame->time_ns > obs->last_observation_time_ns + expiration_ns)
{ {
// Re-observe matching expired observation // Observation expired, re-init
obs->initial_observation_time_ns = frame->time_ns;
}
DllQueueRemove(V.first_observation, V.last_observation, obs); DllQueueRemove(V.first_observation, V.last_observation, obs);
DllQueuePush(V.first_observation, V.last_observation, obs); DllQueuePush(V.first_observation, V.last_observation, obs);
obs->time_ns = frame->time_ns;
}
} }
else else
{ {
if (V.first_observation && V.first_observation->time_ns + expiration_ns <= frame->time_ns) if (V.first_observation && frame->time_ns > V.first_observation->last_observation_time_ns + expiration_ns)
{ {
// Remove expired observation for reuse // Remove expired observation for reuse
obs = V.first_observation; obs = V.first_observation;
@ -1935,14 +1938,14 @@ void V_TickForever(WaveLaneCtx *lane)
obs = PushStruct(perm, V_Observation); obs = PushStruct(perm, V_Observation);
} }
obs->key = key.v; obs->key = key.v;
obs->time_ns = frame->time_ns; obs->initial_observation_time_ns = frame->time_ns;
DllQueuePush(V.first_observation, V.last_observation, obs); DllQueuePush(V.first_observation, V.last_observation, obs);
DllQueuePushNP(bin->first, bin->last, obs, next_in_bin, prev_in_bin); DllQueuePushNP(bin->first, bin->last, obs, next_in_bin, prev_in_bin);
} }
observation_time_ns = obs->time_ns; obs->last_observation_time_ns = frame->time_ns;
} }
ent->observation_time_ns = observation_time_ns; ent->last_observation_time_ns = frame->time_ns;
ent->has_observed = observation_time_ns < frame->time_ns; ent->is_first_observation = obs->initial_observation_time_ns == frame->time_ns;
} }
@ -1950,6 +1953,61 @@ void V_TickForever(WaveLaneCtx *lane)
// for (P_Ent *ent = P_FirstEnt(local_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
// {
// P_EntKey key = ent->key;
// i64 expiration_ns = P_ObservationLifetimeNs;
// i64 observation_time_ns = 0;
// {
// V_ObservationBin *bin = &V.observation_bins[key.v % countof(V.observation_bins)];
// V_Observation *obs = bin->first;
// for (; obs; obs = obs->next)
// {
// if (obs->key == key.v)
// {
// break;
// }
// }
// if (obs)
// {
// if (obs->time_ns + expiration_ns > frame->time_ns)
// {
// // Re-observe matching expired observation
// DllQueueRemove(V.first_observation, V.last_observation, obs);
// DllQueuePush(V.first_observation, V.last_observation, obs);
// obs->time_ns = frame->time_ns;
// }
// }
// else
// {
// if (V.first_observation && V.first_observation->time_ns + expiration_ns <= frame->time_ns)
// {
// // Remove expired observation for reuse
// obs = V.first_observation;
// V_ObservationBin *old_bin = &V.observation_bins[obs->key % countof(V.observation_bins)];
// DllQueueRemove(V.first_observation, V.last_observation, obs);
// DllQueueRemoveNP(old_bin->first, old_bin->last, obs, next_in_bin, prev_in_bin);
// }
// else
// {
// obs = PushStruct(perm, V_Observation);
// }
// obs->key = key.v;
// obs->time_ns = frame->time_ns;
// DllQueuePush(V.first_observation, V.last_observation, obs);
// DllQueuePushNP(bin->first, bin->last, obs, next_in_bin, prev_in_bin);
// }
// observation_time_ns = obs->time_ns;
// }
// ent->observation_time_ns = observation_time_ns;
// ent->has_observed = observation_time_ns < frame->time_ns;
// }
@ -2483,80 +2541,6 @@ void V_TickForever(WaveLaneCtx *lane)
// //////////////////////////////
// //- Push test bullet particles
// // TODO: Not like this
// for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
// {
// if (bullet->is_bullet)
// {
// // FIXME: Use 'last visible' pos
// P_Ent *old_bullet = P_EntFromKey(prev_local_frame, bullet->key);
// Vec2 start = old_bullet->xf.t;
// Vec2 end = bullet->xf.t;
// if (P_IsEntNil(old_bullet))
// {
// start = end;
// }
// b32 skip = 0;
// if (bullet->has_hit)
// {
// Vec2 hit_pos = bullet->hit_entry;
// if (DotVec2(SubVec2(hit_pos, start), SubVec2(end, start)) < 0)
// {
// skip = 1;
// }
// // V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, start), Color_Red);
// // V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, end), Color_Purple);
// end = hit_pos;
// }
// if (!skip)
// {
// {
// f32 trail_len = Vec2Len(SubVec2(end, start));
// f32 particles_count = trail_len * frame->dt * Kibi(8); // Particles per meter per second
// particles_count = MaxF32(particles_count, 1);
// f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
// // f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
// V_Emitter emitter = Zi;
// {
// emitter.kind = V_ParticleKind_BulletTrail;
// emitter.count = particles_count;
// f32 angle_spread = Tau / 4.0;
// emitter.angle.min = angle - angle_spread / 2;
// emitter.angle.max = angle + angle_spread / 2;
// emitter.pos.p0 = start;
// emitter.pos.p1 = end;
// // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
// // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1));
// emitter.speed.min = -1;
// emitter.speed.max = 1;
// }
// V_PushParticles(emitter);
// }
// }
// }
// }
////////////////////////////// //////////////////////////////
//- Push test impact particles //- Push test impact particles
@ -2566,24 +2550,15 @@ void V_TickForever(WaveLaneCtx *lane)
// if (0) // if (0)
{ {
for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) for (P_Ent *hit = P_FirstEnt(local_frame); !P_IsEntNil(hit); hit = P_NextEnt(hit))
{ {
if (bullet->is_bullet && bullet->has_hit) if (hit->is_hit && hit->is_first_observation)
{ {
// FIXME: Use actual velocity P_MaterialKind material = hit->hit_material;
P_Ent *old_bullet = P_EntFromKey(prev_local_frame, bullet->key);
Vec2 start = old_bullet->xf.t;
Vec2 end = bullet->xf.t;
if (P_IsEntNil(old_bullet))
{
start = end;
}
P_MaterialKind material = bullet->hit_material; Vec2 hit_entry = hit->hit_entry;
Vec2 hit_entry_normal = hit->hit_entry_normal;
Vec2 hit_entry = bullet->hit_entry; Vec2 hit_entry_velocity = hit->hit_entry_velocity;
Vec2 hit_entry_normal = bullet->hit_entry_normal;
Vec2 hit_entry_velocity = bullet->hit_entry_velocity;
////////////////////////////// //////////////////////////////
//- Wall particles //- Wall particles
@ -2699,6 +2674,232 @@ void V_TickForever(WaveLaneCtx *lane)
// //////////////////////////////
// //- Push test bullet particles
// // TODO: Not like this
// for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
// {
// if (bullet->is_bullet)
// {
// // FIXME: Use 'last visible' pos
// P_Ent *old_bullet = P_EntFromKey(prev_local_frame, bullet->key);
// Vec2 start = old_bullet->xf.t;
// Vec2 end = bullet->xf.t;
// if (P_IsEntNil(old_bullet))
// {
// start = end;
// }
// b32 skip = 0;
// if (bullet->has_hit)
// {
// Vec2 hit_pos = bullet->hit_entry;
// if (DotVec2(SubVec2(hit_pos, start), SubVec2(end, start)) < 0)
// {
// skip = 1;
// }
// // V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, start), Color_Red);
// // V_DrawPoint(MulAffineVec2(frame->af.world_to_screen, end), Color_Purple);
// end = hit_pos;
// }
// if (!skip)
// {
// {
// f32 trail_len = Vec2Len(SubVec2(end, start));
// f32 particles_count = trail_len * frame->dt * Kibi(8); // Particles per meter per second
// particles_count = MaxF32(particles_count, 1);
// f32 angle = AngleFromVec2(PerpVec2(SubVec2(end, start)));
// // f32 angle = AngleFromVec2(NegVec2(SubVec2(end, start)));
// V_Emitter emitter = Zi;
// {
// emitter.kind = V_ParticleKind_BulletTrail;
// emitter.count = particles_count;
// f32 angle_spread = Tau / 4.0;
// emitter.angle.min = angle - angle_spread / 2;
// emitter.angle.max = angle + angle_spread / 2;
// emitter.pos.p0 = start;
// emitter.pos.p1 = end;
// // emitter.color_lin = LinearFromSrgb(VEC4(0, 1, 0, 1));
// // emitter.color_lin = LinearFromSrgb(VEC4(0.8, 0.6, 0.2, 1));
// emitter.speed.min = -1;
// emitter.speed.max = 1;
// }
// V_PushParticles(emitter);
// }
// }
// }
// }
// //////////////////////////////
// //- Push test impact particles
// // TODO: Not like this
// // if (0)
// {
// for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
// {
// if (bullet->is_bullet && bullet->has_hit)
// {
// // FIXME: Use actual velocity
// P_Ent *old_bullet = P_EntFromKey(prev_local_frame, bullet->key);
// Vec2 start = old_bullet->xf.t;
// Vec2 end = bullet->xf.t;
// if (P_IsEntNil(old_bullet))
// {
// start = end;
// }
// P_MaterialKind material = bullet->hit_material;
// Vec2 hit_entry = bullet->hit_entry;
// Vec2 hit_entry_normal = bullet->hit_entry_normal;
// Vec2 hit_entry_velocity = bullet->hit_entry_velocity;
// //////////////////////////////
// //- Wall particles
// if (material != P_MaterialKind_Flesh)
// {
// //- Wall debris
// {
// V_Emitter emitter = Zi;
// {
// // emitter.flags |= V_ParticleFlag_PruneWhenStill;
// // emitter.flags |= V_ParticleFlag_StainOnPrune;
// emitter.kind = V_ParticleKind_Debris;
// emitter.count = 4;
// emitter.pos.p0 = emitter.pos.p1 = hit_entry;
// emitter.speed.min = 0;
// emitter.speed.max = 20;
// // emitter.velocity_falloff = 5;
// // emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5;
// Vec2 dir = hit_entry_normal;
// f32 angle = AngleFromVec2(dir);
// f32 angle_spread = Tau * 0.5;
// emitter.angle.min = angle - angle_spread / 2;
// emitter.angle.max = angle + angle_spread / 2;
// // emitter.lifetime = 0.25;
// // emitter.lifetime = 0.05;
// // emitter.lifetime = 0.04;
// // emitter.lifetime_spread = emitter.lifetime * 2;
// }
// V_PushParticles(emitter);
// }
// //- Wall dust
// // {
// // V_Emitter emitter = Zi;
// // {
// // emitter.kind = V_ParticleKind_Smoke;
// // emitter.count = 128;
// // emitter.pos.p0 = emitter.pos.p1 = hit_entry;
// // // emitter.color_lin = LinearFromSrgb(VEC4(0.5, 0.5, 0.5, 0.75));
// // emitter.speed.min = 10;
// // emitter.speed.max = 20;
// // // emitter.velocity_falloff = 12;
// // // emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5;
// // Vec2 dir = hit_entry_normal;
// // f32 angle = AngleFromVec2(dir);
// // f32 angle_spread = Tau * 0.1;
// // emitter.angle.min = angle - angle_spread / 2;
// // emitter.angle.max = angle + angle_spread / 2;
// // }
// // V_PushParticles(emitter);
// // }
// }
// //////////////////////////////
// //- Blood particles
// if (material == P_MaterialKind_Flesh)
// {
// {
// V_Emitter emitter = Zi;
// emitter.kind = V_ParticleKind_BloodTrail;
// // emitter.kind = V_ParticleKind_BloodDebris;
// Vec2 dir = NormVec2(NegVec2(hit_entry_velocity));
// f32 angle = AngleFromVec2(dir);
// // f32 angle = 0;
// // f32 angle_spread = Tau * 0.25;
// f32 angle_spread = TweakFloat("Emitter angle spread", 0.1, 0, 1) * Tau;
// // f32 angle_spread = Tau;
// // f32 angle_spread = 0;
// // f32 speed = 5;
// // f32 speed = 25;
// f32 speed = 50;
// // f32 speed = 100;
// f32 speed_spread = speed * 2;
// emitter.pos.p0 = emitter.pos.p1 = hit_entry;
// emitter.speed.min = speed - speed_spread * 0.5;
// emitter.speed.max = speed + speed_spread * 0.5;
// emitter.angle.min = angle - angle_spread * 0.5;
// emitter.angle.max = angle + angle_spread * 0.5;
// emitter.count = TweakFloat("Emitter count", 1, 0, 100) * (Kibi(1) * frame->dt);
// V_PushParticles(emitter);
// }
// }
// // V_DrawPoint(victim_raycast.p, Color_Green);
// // V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White);
// }
// }
// }
////////////////////////////// //////////////////////////////
//- Push test emitter //- Push test emitter

View File

@ -239,7 +239,8 @@ Struct(V_Observation)
V_Observation *next_in_bin; V_Observation *next_in_bin;
V_Observation *prev_in_bin; V_Observation *prev_in_bin;
u64 key; u64 key;
i64 time_ns; i64 initial_observation_time_ns;
i64 last_observation_time_ns;
}; };
Struct(V_ObservationBin) Struct(V_ObservationBin)