From e6d842227f6e29968624a27baa10509b20f7921c Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 20 Feb 2026 21:50:25 -0600 Subject: [PATCH] bomb ricochet --- src/base/base_math.c | 8 +- src/base/base_math.h | 1 - src/pp/pp.c | 626 +++++++++++++++++++++++------------- src/pp/pp.h | 11 +- src/pp/pp_sim/pp_sim_core.c | 12 + src/pp/pp_vis/pp_vis_core.c | 314 +++--------------- src/pp/pp_vis/pp_vis_core.h | 1 + 7 files changed, 463 insertions(+), 510 deletions(-) diff --git a/src/base/base_math.c b/src/base/base_math.c index 0f768ce7..7c1c52db 100644 --- a/src/base/base_math.c +++ b/src/base/base_math.c @@ -1151,16 +1151,10 @@ Vec2 MulXformVec2(Xform xf, Vec2 v) Xform NormXform(Xform xf) { - xf.r = NormRot(xf.r); + xf.r = NormVec2(xf.r); return xf; } -Vec2 NormRot(Vec2 r) -{ - r = Vec2WithLen(r, 1); - return r; -} - //////////////////////////////////////////////////////////// //~ Line diff --git a/src/base/base_math.h b/src/base/base_math.h index 7b08795a..5e910cb6 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -525,7 +525,6 @@ Xform InvertXform(Xform xf); Vec2 MulXformVec2(Xform xf, Vec2 v); Xform NormXform(Xform xf); -Vec2 NormRot(Vec2 r); //////////////////////////////////////////////////////////// //~ Line diff --git a/src/pp/pp.c b/src/pp/pp.c index 899f1afd..0f39c6ca 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -70,6 +70,11 @@ P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b) return (P_ConstraintKey) { .v = MixU64s(a, b) }; } +P_EntKey P_EntKeyFromU64(u64 v) +{ + return (P_EntKey) { .v = v }; +} + //////////////////////////////////////////////////////////// //~ Rand helpers @@ -1525,7 +1530,9 @@ void P_UniqueSpaceEntriesFromRay(Arena *arena, P_SpaceEntryList *result, i32 spa Vec2I32 dda_pos = dda_start; b32 done = 0; - while (!done) + u32 max_iterations = 512; + u32 iteration_idx = 0; + for (; iteration_idx < max_iterations && !done; ++iteration_idx) { if (dda_pos.x >= 0 && dda_pos.y >= 0 && dda_pos.x < P_WorldPitch && dda_pos.y < P_WorldPitch) { @@ -1592,6 +1599,12 @@ void P_UniqueSpaceEntriesFromRay(Arena *arena, P_SpaceEntryList *result, i32 spa t_max.y += t_delta.y; } } + + if (iteration_idx >= max_iterations) + { + // This should never happen unless we have a bug in the algorithm + Assert(0); + } } EndScratch(scratch); } @@ -2241,7 +2254,7 @@ void P_StepFrame(P_Frame *frame) weapon->is_weapon = 1; weapon->key = P_RandEntKey(); weapon->source = guy->key; - // weapon->is_uzi = 1; + // weapon->1is_uzi = 1; weapon->is_launcher = 1; guy->weapon = weapon->key; } @@ -2300,8 +2313,8 @@ void P_StepFrame(P_Frame *frame) if (frame->time_ns - roll_timeout_ns - roll_time_ns > guy->last_roll_time_ns || guy->last_roll_time_ns == 0) { guy->last_roll_time_ns = frame->time_ns; - // guy->last_roll_dir = NormRot(guy->control.move); - guy->last_roll_dir = NormRot(guy->control.look); + // guy->last_roll_dir = NormVec2(guy->control.move); + guy->last_roll_dir = NormVec2(guy->control.look); } } } @@ -2318,15 +2331,15 @@ void P_StepFrame(P_Frame *frame) // Dampen movement { - if (Vec2Len(guy->solved_v) > 0.001) + if (Vec2Len(guy->v) > 0.001) { f32 damp_force = TweakFloat("Guy damp force", 50, 0, 100); - Vec2 damp = MulVec2(NegVec2(guy->solved_v), damp_force * sim_dt); - guy->solved_v = AddVec2(guy->solved_v, damp); + Vec2 damp = MulVec2(NegVec2(guy->v), damp_force * sim_dt); + guy->v = AddVec2(guy->v, damp); } else { - guy->solved_v = VEC2(0, 0); + guy->v = VEC2(0, 0); } } @@ -2341,8 +2354,8 @@ void P_StepFrame(P_Frame *frame) b32 is_rolling = P_IsEntRolling(frame, guy); if (is_rolling) { - // Vec2 roll_dir = NormRot(guy->last_roll_dir); - Vec2 roll_dir = NormRot(guy->xf.r); + // Vec2 roll_dir = NormVec2(guy->last_roll_dir); + Vec2 roll_dir = NormVec2(guy->xf.r); move = roll_dir; // look = roll_dir; @@ -2359,14 +2372,14 @@ void P_StepFrame(P_Frame *frame) // Integrate linear movement { - Vec2 new_velocity = guy->solved_v; + Vec2 new_velocity = guy->v; new_velocity = AddVec2(new_velocity, MulVec2(move, move_force * sim_dt)); if (Vec2Len(new_velocity) > max_speed) { new_velocity = Vec2WithLen(new_velocity, max_speed); } - guy->solved_v = new_velocity; + guy->v = new_velocity; } // Integrate look @@ -2375,7 +2388,7 @@ void P_StepFrame(P_Frame *frame) f32 desired_angle = AngleFromVec2(look); f32 diff = UnwindAngleF32(desired_angle - cur_angle); f32 look_force = 1.0 / (sim_dt * sim_dt) * turn_rate; - guy->solved_w = diff * sim_dt * look_force; + guy->w = diff * sim_dt * look_force; } } } @@ -2680,10 +2693,10 @@ void P_StepFrame(P_Frame *frame) P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); - Vec2 v0 = ent0->solved_v; - Vec2 v1 = ent1->solved_v; - f32 w0 = ent0->solved_w; - f32 w1 = ent1->solved_w; + Vec2 v0 = ent0->v; + Vec2 v1 = ent1->v; + f32 w0 = ent0->w; + f32 w1 = ent1->w; Vec2 normal = constraint->normal; Vec2 tangent = PerpVec2(normal); @@ -2705,13 +2718,13 @@ void P_StepFrame(P_Frame *frame) if (!P_IsEntNil(ent0)) { - ent0->solved_v = v0; - ent0->solved_w = w0; + ent0->v = v0; + ent0->w = w0; } if (!P_IsEntNil(ent1)) { - ent1->solved_v = v1; - ent1->solved_w = w1; + ent1->v = v1; + ent1->w = w1; } } } @@ -2733,10 +2746,10 @@ void P_StepFrame(P_Frame *frame) f32 inv_m1 = constraint->inv_m1; f32 inv_i0 = constraint->inv_i0; f32 inv_i1 = constraint->inv_i1; - Vec2 v0 = ent0->solved_v; - Vec2 v1 = ent1->solved_v; - f32 w0 = ent0->solved_w; - f32 w1 = ent1->solved_w; + Vec2 v0 = ent0->v; + Vec2 v1 = ent1->v; + f32 w0 = ent0->w; + f32 w1 = ent1->w; Vec2 center0 = constraint->static_center0; Vec2 center1 = constraint->static_center1; @@ -2837,13 +2850,13 @@ void P_StepFrame(P_Frame *frame) if (!P_IsEntNil(ent0)) { - ent0->solved_v = v0; - ent0->solved_w = w0; + ent0->v = v0; + ent0->w = w0; } if (!P_IsEntNil(ent1)) { - ent1->solved_v = v1; - ent1->solved_w = w1; + ent1->v = v1; + ent1->w = w1; } } @@ -2876,13 +2889,13 @@ void P_StepFrame(P_Frame *frame) if (!P_IsEntNil(ent0)) { - ent0->solved_v = v0; - ent0->solved_w = w0; + ent0->v = v0; + ent0->w = w0; } if (!P_IsEntNil(ent1)) { - ent1->solved_v = v1; - ent1->solved_w = w1; + ent1->v = v1; + ent1->w = w1; } } } @@ -2892,11 +2905,11 @@ void P_StepFrame(P_Frame *frame) for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) { - if (!is_predicting || ent == local_guy) + if (!ent->is_bullet && (!is_predicting || ent == local_guy)) { Xform xf = ent->xf; - xf.t = AddVec2(xf.t, MulVec2(ent->solved_v, solver_dt)); - xf.r = RotateVec2Angle(xf.r, ent->solved_w * solver_dt); + xf.t = AddVec2(xf.t, MulVec2(ent->v, solver_dt)); + xf.r = RotateVec2Angle(xf.r, ent->w * solver_dt); ent->xf = xf; } } @@ -2908,7 +2921,19 @@ void P_StepFrame(P_Frame *frame) P_Space post_solve_ents_space = P_SpaceFromEnts(scratch.arena, frame); ////////////////////////////// - //- Fire bullets + //- Spawn bullets + + + + + + + + + + + + @@ -2920,31 +2945,121 @@ void P_StepFrame(P_Frame *frame) for (P_Ent *firer = P_FirstEnt(frame); !P_IsEntNil(firer); firer = P_NextEnt(firer)) { P_Ent *weapon = P_EntFromKey(frame, firer->weapon); - if (weapon->is_weapon && firer->control.fire_held || firer->control.fire_presses) - // if (weapon->is_weapon && firer->control.fire_presses) + // if (weapon->is_weapon && (firer->control.fire_held || firer->control.fire_presses)) + if (weapon->is_weapon && firer->control.fire_presses) { - // i64 fire_delta_ns = frame->time_ns - firer->last_fire_ns; - - // i64 single_bullet_delta_ns = NsFromSeconds(1) / firer->fire_rate; - - // i64 tick_bullets_count = sim_dt * firer->fire_rate; - f32 fire_rate = 50; f32 bullets_per_fire = 1; - f32 spread = Tau * 0.05; - // f32 spread = Tau * 0.01; - f32 bullet_speed = TweakFloat("Bullet speed", 100, 1, 100); - f32 bomb_speed = 10; - // f32 tweak_speed = TweakFloat("Bullet speed", 1, 1, 100); + b32 can_fire = (weapon->last_fire_ns + NsFromSeconds(1.0 / fire_rate)) <= frame->time_ns; - b32 can_fire = (firer->last_fire_ns + NsFromSeconds(1.0 / fire_rate)) <= frame->time_ns; + if (can_fire) + { + i64 tick_bullets_count = bullets_per_fire; + if (tick_bullets_count > 0) + { + for (i64 bullet_idx = 0; bullet_idx < tick_bullets_count; ++bullet_idx) + { + P_Ent *bullet = P_PushTempEnt(scratch.arena, &bullets_to_spawn); + bullet->is_bullet = 1; + + // TDOO: More specific key with seed that only increments on player control (for less misprediction) + bullet->key = P_EntKeyFromU64(P_RandU64FromEnt(firer)); + + if (weapon->is_launcher) + { + bullet->is_bomb = 1; + } + + bullet->source = weapon->key; + bullet->damage_attribution = firer->source; + } + } + weapon->last_fire_ns = frame->time_ns; + } + } + } + P_SpawnEntsFromList(frame, bullets_to_spawn); + } + + + + + + ////////////////////////////// + //- Update bullets + + + + + + + + + // TODO: Separate 'hits' from bullets, so that bullets can have multiple hits + + for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) + { + if (bullet->is_bullet) + { + P_Ent *weapon = P_EntFromKey(frame, bullet->source); + P_Ent *firer = P_EntFromKey(frame, weapon->source); + P_Ent *damager = P_EntFromKey(frame, bullet->damage_attribution); + + b32 is_first_bullet_tick = bullet->created_at_tick == frame->tick; + + ////////////////////////////// + //- Bullet properties + + f32 spread = Tau * 0.05; + // f32 spread = Tau * 0.01; + + b32 should_ricochet = 0; + + f32 initial_speed = 1; + f32 speed_falloff = 0; + if (weapon->is_uzi) + { + initial_speed = TweakFloat("Bullet speed", 100, 1, 100); + } + else if (weapon->is_launcher) + { + should_ricochet = 1; + initial_speed = 50; + // initial_speed = 100; + speed_falloff = 5; + } + + ////////////////////////////// + //- Initialize + + // if (bullet->created_at_tick != frame->tick) + // { + // Vec2 start = bullet->bullet_start; + // Vec2 end = bullet->bullet_end; + // Vec2 vel = SubVec2(end, start); + // bullet->bullet_start = end; + // bullet->bullet_end = AddVec2(end, vel); + // bullet->xf.t = bullet->bullet_start; + // bullet->xf.r = NormVec2(vel); + // } + + Struct(BulletPath) + { + BulletPath *next; + Vec2 start; + Vec2 end; + }; + BulletPath *first_bullet_path = 0; + BulletPath *last_bullet_path = 0; + + if (is_first_bullet_tick) + { Vec2 fire_pos = Zi; Vec2 fire_dir = Zi; Vec2 fire_base0 = Zi; Vec2 fire_base1 = Zi; - if (can_fire) { Vec2 look = firer->control.look; P_Anim anim = P_AnimFromEnt(frame, firer); @@ -2990,7 +3105,7 @@ void P_StepFrame(P_Frame *frame) SPR_Ray fire_ray = wep.rays[SPR_RayKind_Ap]; fire_pos = MulAffineVec2(wep_pix_to_world_af, fire_ray.pos); - fire_dir = NormRot(MulAffineBasisVec2(wep_pix_to_world_af, fire_ray.dir)); + fire_dir = NormVec2(MulAffineBasisVec2(wep_pix_to_world_af, fire_ray.dir)); fire_base0 = MulVec2(PerpVec2(NegVec2(firer->xf.r)), WedgeVec2(firer->xf.r, SubVec2(firer->xf.t, fire_pos))); fire_base0 = AddVec2(fire_base0, firer->xf.t); @@ -3005,96 +3120,14 @@ void P_StepFrame(P_Frame *frame) P_DebugDrawPoint(fire_base0, Color_Yellow); P_DebugDrawPoint(fire_base1, Color_Green); P_DebugDrawPoint(fire_pos, Color_Red); + + bullet->xf.t = fire_pos; + bullet->xf.r = NormVec2(fire_dir); + + bullet->v = MulVec2(NormVec2(fire_dir), initial_speed); + bullet->v = AddVec2(bullet->v, firer->v); } - // FIXME: Prevent obstructed weapons from firing through walls - - if (can_fire) - { - if (weapon->is_uzi) - { - i64 tick_bullets_count = bullets_per_fire; - if (tick_bullets_count > 0) - { - P_Shape firer_world_shape = P_WorldShapeFromEnt(firer); - - // Vec2 fire_pos = P_EdgePointFromShape(firer_world_shape, firer->control.look); - // Vec2 fire_dir = firer->control.look; - - for (i64 bullet_idx = 0; bullet_idx < tick_bullets_count; ++bullet_idx) - { - P_Ent *bullet = P_PushTempEnt(scratch.arena, &bullets_to_spawn); - bullet->is_bullet = 1; - bullet->key = P_RandEntKey(); - - f32 rand_speed = Norm24(P_RandU64FromEnt(firer)) - 0.5; - f32 rand_angle = Norm24(P_RandU64FromEnt(firer)) - 0.5; - - f32 speed = bullet_speed * sim_dt; - f32 angle = AngleFromVec2(fire_dir); - - speed += (speed * 0.5) * rand_speed; - angle += rand_angle * spread; - - Vec2 dir = Vec2FromAngle(angle); - - bullet->bullet_base0 = fire_base0; - bullet->bullet_base1 = fire_base1; - bullet->bullet_start = fire_pos; - bullet->bullet_end = AddVec2(bullet->bullet_start, MulVec2(dir, speed)); - bullet->xf.t = bullet->bullet_start; - bullet->xf.r = NormRot(dir); - bullet->source = weapon->key; - bullet->damage_attribution = firer->source; - } - } - firer->last_fire_ns = frame->time_ns; - } - else if (weapon->is_launcher) - { - i64 tick_bullets_count = bullets_per_fire; - if (tick_bullets_count > 0) - { - P_Shape firer_world_shape = P_WorldShapeFromEnt(firer); - - // Vec2 fire_pos = P_EdgePointFromShape(firer_world_shape, firer->control.look); - // Vec2 fire_dir = firer->control.look; - - for (i64 bullet_idx = 0; bullet_idx < tick_bullets_count; ++bullet_idx) - { - P_Ent *bomb = P_PushTempEnt(scratch.arena, &bullets_to_spawn); - bomb->is_bomb = 1; - bomb->is_bullet = 1; - bomb->key = P_RandEntKey(); - - f32 rand_speed = Norm24(P_RandU64FromEnt(firer)) - 0.5; - f32 rand_angle = Norm24(P_RandU64FromEnt(firer)) - 0.5; - - f32 speed = bomb_speed * sim_dt; - f32 angle = AngleFromVec2(fire_dir); - - speed += (speed * 0.5) * rand_speed; - angle += rand_angle * spread; - - Vec2 dir = Vec2FromAngle(angle); - - bomb->bullet_base0 = fire_base0; - bomb->bullet_base1 = fire_base1; - bomb->bullet_start = fire_pos; - bomb->xf.t = bomb->bullet_start; - bomb->xf.r = NormRot(dir); - bomb->bullet_end = AddVec2(bomb->bullet_start, MulVec2(dir, speed)); - bomb->source = weapon->key; - bomb->damage_attribution = firer->source; - } - } - firer->last_fire_ns = frame->time_ns; - } - } - } - } - P_SpawnEntsFromList(frame, bullets_to_spawn); - } @@ -3102,70 +3135,6 @@ void P_StepFrame(P_Frame *frame) - - - - - - - - - - - - - - - - - - - - - - ////////////////////////////// - //- Update bullets - - // TODO: Separate 'hits' from bullets, so that bullets can have multiple hits - - for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) - { - if (bullet->is_bullet) - { - P_Ent *bullet_weapon = P_EntFromKey(frame, bullet->source); - P_Ent *bullet_guy = P_EntFromKey(frame, bullet_weapon->source); - P_Ent *bullet_damager = P_EntFromKey(frame, bullet->damage_attribution); - - - - if (bullet->created_at_tick != frame->tick) - { - Vec2 start = bullet->bullet_start; - Vec2 end = bullet->bullet_end; - Vec2 vel = SubVec2(end, start); - bullet->bullet_start = end; - bullet->bullet_end = AddVec2(end, vel); - bullet->xf.t = bullet->bullet_start; - bullet->xf.r = NormRot(vel); - } - - - - - - bullet->has_hit = 0; - - Struct(BulletPath) - { - BulletPath *next; - Vec2 start; - Vec2 end; - }; - BulletPath *first_bullet_path = 0; - BulletPath *last_bullet_path = 0; - - if (bullet->created_at_tick == frame->tick) - { // On bullet's first tick, we want to ensure that the firer/weapon // wasn't obstructed (e.g. to prevent shooting through walls), so we // insert a path from the bullet's base to its starting position before @@ -3174,23 +3143,30 @@ void P_StepFrame(P_Frame *frame) // Firer origin -> weapon chamber path BulletPath *path = PushStruct(scratch.arena, BulletPath); SllQueuePush(first_bullet_path, last_bullet_path, path); - path->start = bullet->bullet_base0; - path->end = bullet->bullet_base1; + path->start = fire_base0; + path->end = fire_base1; } { // Weapon chamber -> bullet start path BulletPath *path = PushStruct(scratch.arena, BulletPath); SllQueuePush(first_bullet_path, last_bullet_path, path); - path->start = bullet->bullet_base1; - path->end = bullet->bullet_start; + path->start = fire_base1; + path->end = fire_pos; } } + Vec2 dir = MulVec2(bullet->v, sim_dt); + Vec2 p0 = bullet->xf.t; + Vec2 p1 = p0; + + // Cur pos -> next pos path + if (!is_first_bullet_tick) { + p1 = AddVec2(p0, dir); BulletPath *path = PushStruct(scratch.arena, BulletPath); SllQueuePush(first_bullet_path, last_bullet_path, path); - path->start = bullet->bullet_start; - path->end = bullet->bullet_end; + path->start = p0; + path->end = p1; } P_EntKey victim_key = Zi; @@ -3218,7 +3194,7 @@ void P_StepFrame(P_Frame *frame) { P_SpaceEntry *entry = &entry_node->entry; P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; - if (!P_MatchEntKey(potential_victim_key, bullet_guy->key) || P_IsEntKeyNil(bullet_guy->key)) + if (!P_MatchEntKey(potential_victim_key, firer->key) || P_IsEntKeyNil(firer->key)) { P_Shape potential_victim_shape = entry->shape; P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); @@ -3247,14 +3223,21 @@ void P_StepFrame(P_Frame *frame) } P_Ent *victim = P_EntFromKey(frame, victim_key); - // TODO: Truncate bullet trail + bullet->has_hit = 0; + + Vec2 final_pos = p1; if (hit) { + Vec2 normal = victim_raycast.normal; + bullet->has_hit = 1; bullet->hit_entry = victim_raycast.p; - bullet->hit_entry_normal = victim_raycast.normal; + bullet->hit_entry_normal = normal; + + // FIXME: Use relative velocity at collision point + bullet->hit_entry_velocity = bullet->v; + // bullet->bullet_end = bullet->hit_entry; - bullet->exists = 0; if (victim->is_guy) { bullet->hit_material = P_MaterialKind_Flesh; @@ -3263,14 +3246,35 @@ void P_StepFrame(P_Frame *frame) { bullet->hit_material = P_MaterialKind_Wall; } + + // Reflect velocity along normal + if (should_ricochet) + { + bullet->v = SubVec2(bullet->v, MulVec2(normal, 2 * DotVec2(bullet->v, normal))); + bullet->v = MulVec2(bullet->v, 0.5); + } + else + { + bullet->exists = 0; + } + + 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)); } + bullet->xf.t = final_pos; + if (Vec2LenSq(dir) > (0.001 * 0.001)) + { + bullet->xf.r = NormVec2(dir); + } + bullet->v = MulVec2(bullet->v, 1.0 - SaturateF32(speed_falloff * sim_dt)); + // TODO: Remove this if (!P_IsEntNil(victim)) { - if (bullet_damager->is_player) + if (damager->is_player) { - victim->damage_attribution = bullet_damager->key; + victim->damage_attribution = damager->key; } victim->health -= 0.25; } @@ -3280,8 +3284,8 @@ void P_StepFrame(P_Frame *frame) bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); if ( - bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y || - bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y + bullet->xf.t.x < bounds.p0.x || bullet->xf.t.y < bounds.p0.y || + bullet->xf.t.x > bounds.p1.x || bullet->xf.t.y > bounds.p1.y ) { bullet->exists = 0; @@ -3289,6 +3293,190 @@ void P_StepFrame(P_Frame *frame) } } + + + + + + + + + + + + + + + + + // // TODO: Separate 'hits' from bullets, so that bullets can have multiple hits + + // for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) + // { + // if (bullet->is_bullet) + // { + // P_Ent *weapon = P_EntFromKey(frame, bullet->source); + // P_Ent *firer = P_EntFromKey(frame, weapon->source); + // P_Ent *damager = P_EntFromKey(frame, bullet->damage_attribution); + + + + // if (bullet->created_at_tick != frame->tick) + // { + // Vec2 start = bullet->bullet_start; + // Vec2 end = bullet->bullet_end; + // Vec2 vel = SubVec2(end, start); + // bullet->bullet_start = end; + // bullet->bullet_end = AddVec2(end, vel); + // bullet->xf.t = bullet->bullet_start; + // bullet->xf.r = NormVec2(vel); + // } + + + + + + // bullet->has_hit = 0; + + // Struct(BulletPath) + // { + // BulletPath *next; + // Vec2 start; + // Vec2 end; + // }; + // BulletPath *first_bullet_path = 0; + // BulletPath *last_bullet_path = 0; + + // if (bullet->created_at_tick == frame->tick) + // { + // // On bullet's first tick, we want to ensure that the firer/weapon + // // wasn't obstructed (e.g. to prevent shooting through walls), so we + // // insert a path from the bullet's base to its starting position before + // // its actual firing path + // { + // // Firer origin -> weapon chamber path + // BulletPath *path = PushStruct(scratch.arena, BulletPath); + // SllQueuePush(first_bullet_path, last_bullet_path, path); + // path->start = bullet->bullet_base0; + // path->end = bullet->bullet_base1; + // } + // { + // // Weapon chamber -> bullet start path + // BulletPath *path = PushStruct(scratch.arena, BulletPath); + // SllQueuePush(first_bullet_path, last_bullet_path, path); + // path->start = bullet->bullet_base1; + // path->end = bullet->bullet_start; + // } + // } + + // { + // BulletPath *path = PushStruct(scratch.arena, BulletPath); + // SllQueuePush(first_bullet_path, last_bullet_path, path); + // path->start = bullet->bullet_start; + // path->end = bullet->bullet_end; + // } + + // P_EntKey victim_key = Zi; + // P_RaycastResult victim_raycast = Zi; + // b32 hit = 0; + // { + // for (BulletPath *path = first_bullet_path; path && !hit; path = path->next) + // { + // Vec2 path_dir = SubVec2(path->end, path->start); + // P_Space *cast_spaces[] = { + // &world->walls_space, + // &post_solve_ents_space, + // }; + // P_SpaceEntryList cast_entries = Zi; + // P_UniqueSpaceEntriesFromRay( + // scratch.arena, + // &cast_entries, + // countof(cast_spaces), + // cast_spaces, + // path->start, + // path->end + // ); + // f32 closest_len_sq = Inf; + // for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next) + // { + // P_SpaceEntry *entry = &entry_node->entry; + // P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; + // if (!P_MatchEntKey(potential_victim_key, firer->key) || P_IsEntKeyNil(firer->key)) + // { + // P_Shape potential_victim_shape = entry->shape; + // P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); + // Vec2 entrance = entrance_raycast.p; + // if (entrance_raycast.is_intersecting) + // { + // P_RaycastResult exit_raycast = P_RaycastShape(potential_victim_shape, path->start, NegVec2(path_dir)); + // Vec2 exit = exit_raycast.p; + // f32 da = DotVec2(path_dir, SubVec2(entrance, path->start)); + // f32 db = DotVec2(path_dir, SubVec2(exit, path->start)); + // if (db > 0 && (da <= Vec2LenSq(path_dir) || da <= 0)) + // { + // f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, path->start)); + // if (len_sq < closest_len_sq) + // { + // closest_len_sq = len_sq; + // victim_key = potential_victim_key; + // victim_raycast = entrance_raycast; + // hit = 1; + // } + // } + // } + // } + // } + // } + // } + // P_Ent *victim = P_EntFromKey(frame, victim_key); + + // // TODO: Truncate bullet trail + // if (hit) + // { + // bullet->has_hit = 1; + // bullet->hit_entry = victim_raycast.p; + // bullet->hit_entry_normal = victim_raycast.normal; + // // bullet->bullet_end = bullet->hit_entry; + // bullet->exists = 0; + // if (victim->is_guy) + // { + // bullet->hit_material = P_MaterialKind_Flesh; + // } + // else + // { + // bullet->hit_material = P_MaterialKind_Wall; + // } + // } + + // // TODO: Remove this + // if (!P_IsEntNil(victim)) + // { + // if (damager->is_player) + // { + // victim->damage_attribution = damager->key; + // } + // victim->health -= 0.25; + // } + + // // Prune out of bounds bullet + // Rng2 bounds = Zi; + // bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); + // bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); + // if ( + // bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y || + // bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y + // ) + // { + // bullet->exists = 0; + // } + // } + // } + + + + + + ////////////////////////////// //- Kill guys diff --git a/src/pp/pp.h b/src/pp/pp.h index 0523dc96..b0e61cb4 100644 --- a/src/pp/pp.h +++ b/src/pp/pp.h @@ -129,14 +129,11 @@ Struct(P_Ent) //- Bullet / hit b32 is_bullet; - Vec2 bullet_base0; - Vec2 bullet_base1; - Vec2 bullet_start; - Vec2 bullet_end; b32 has_hit; Vec2 hit_entry; Vec2 hit_entry_normal; + Vec2 hit_entry_velocity; P_MaterialKind hit_material; //- Bomb @@ -189,8 +186,8 @@ Struct(P_Ent) //- Solver - Vec2 solved_v; - f32 solved_w; + Vec2 v; // Linear velocity + f32 w; // Angular velocity }; Struct(P_EntListNode) @@ -417,6 +414,7 @@ Enum(P_MsgKind) // Client -> Server P_MsgKind_SaveWorld, P_MsgKind_ResetWorld, + P_MsgKind_ClearBullets, P_MsgKind_Teleport, P_MsgKind_TileEdit, P_MsgKind_Prefab, @@ -586,6 +584,7 @@ b32 P_IsFrameNil(P_Frame *frame); b32 P_MatchEntKey(P_EntKey a, P_EntKey b); b32 P_MatchConstraintKey(P_ConstraintKey a, P_ConstraintKey b); P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b); +P_EntKey P_EntKeyFromU64(u64 v); #define P_FmtKey(key) FmtHandle((key).v) diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index 90c38ce4..bbda5ab4 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -557,6 +557,18 @@ void S_TickForever(WaveLaneCtx *lane) } } } break; + //- Clear bullets + case P_MsgKind_ClearBullets: + { + // TODO: Real reset + for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) + { + if (ent->is_bullet) + { + ent->exists = 0; + } + } + } break; //- Teleport case P_MsgKind_Teleport: { diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 5bdc3c32..a2403499 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -914,8 +914,8 @@ void V_TickForever(WaveLaneCtx *lane) Vec2 look_delta = mouse_delta; look_delta = MulVec2(look_delta, mouse_sensitivity * mouse_scale_factor); - Vec2 prev_crosshair_rot = NormRot(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base)); - Vec2 prev_look_rot = NormRot(prev_frame->look); + Vec2 prev_crosshair_rot = NormVec2(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base)); + Vec2 prev_look_rot = NormVec2(prev_frame->look); Vec2 rot = RotateVec2(prev_look_rot, InvertRot(prev_crosshair_rot)); look_delta = RotateVec2(look_delta, rot); @@ -952,8 +952,6 @@ void V_TickForever(WaveLaneCtx *lane) frame->edit_camera_pos = AddVec2(frame->edit_camera_pos, MulVec2(move, edit_move_speed * frame->dt)); } } - - DEBUGBREAKABLE; } ////////////////////////////// @@ -1002,8 +1000,8 @@ void V_TickForever(WaveLaneCtx *lane) look_ratio = VEC2(0.5, 0.5); } - Vec2 prev_crosshair_rot = NormRot(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base)); - Vec2 prev_look_rot = NormRot(prev_frame->look); + Vec2 prev_crosshair_rot = NormVec2(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base)); + Vec2 prev_look_rot = NormVec2(prev_frame->look); Vec2 rot = RotateVec2(prev_look_rot, InvertRot(prev_crosshair_rot)); Vec2 look_pos = frame->look; @@ -1840,9 +1838,12 @@ void V_TickForever(WaveLaneCtx *lane) } local_world->seed = predict_world->seed; - P_ClearFrames(local_world, I64Min, I64Max); + P_ClearFrames(local_world, I64Min, local_world->last_frame->tick - 1); local_frame = P_PushFrame(local_world, predict_world->last_frame, predict_world->last_frame->tick); + // LogDebugF("First frame: %F, Last frame: %F", FmtSint(local_world->first_frame->tick), FmtSint(local_world->last_frame->tick)); + + P_DebugDrawFrame(local_frame); } @@ -1901,7 +1902,7 @@ void V_TickForever(WaveLaneCtx *lane) //- Compute sprite transforms Affine cur_ent_to_world_af = MulAffineXform(AffineIdentity, ent->xf); - Affine desired_ent_to_world_af = MulAffineXform(AffineIdentity, XformTR(ent->xf.t, NormRot(look))); + Affine desired_ent_to_world_af = MulAffineXform(AffineIdentity, XformTR(ent->xf.t, NormVec2(look))); Affine cur_body_pix_to_world_af = AffineIdentity; Affine cur_wep_pix_to_world_af = AffineIdentity; Affine desired_body_pix_to_world_af = AffineIdentity; @@ -1944,9 +1945,9 @@ void V_TickForever(WaveLaneCtx *lane) SPR_Ray fire_ray = wep.rays[SPR_RayKind_Ap]; Vec2 cur_fire_pos = MulAffineVec2(cur_wep_pix_to_world_af, fire_ray.pos); - Vec2 cur_fire_dir = NormRot(MulAffineBasisVec2(cur_wep_pix_to_world_af, fire_ray.dir)); + Vec2 cur_fire_dir = NormVec2(MulAffineBasisVec2(cur_wep_pix_to_world_af, fire_ray.dir)); Vec2 desired_fire_pos = MulAffineVec2(desired_wep_pix_to_world_af, fire_ray.pos); - Vec2 desired_fire_dir = NormRot(MulAffineBasisVec2(desired_wep_pix_to_world_af, fire_ray.dir)); + Vec2 desired_fire_dir = NormVec2(MulAffineBasisVec2(desired_wep_pix_to_world_af, fire_ray.dir)); Vec2 desired_hold_pos = MulAffineVec2(desired_body_pix_to_world_af, body.rays[SPR_RayKind_Ap].pos); @@ -1962,7 +1963,7 @@ void V_TickForever(WaveLaneCtx *lane) frame->world_guy_origin = desired_ent_to_world_af.og; frame->world_crosshair_base = desired_crosshair_base; - Vec2 look_rot = NormRot(look); + Vec2 look_rot = NormVec2(look); Vec2 line_start = cur_fire_pos; Vec2 line_end = AddVec2(line_start, cur_fire_dir); @@ -2263,8 +2264,14 @@ void V_TickForever(WaveLaneCtx *lane) { if (bullet->is_bullet) { - Vec2 start = bullet->bullet_start; - Vec2 end = bullet->bullet_end; + // FIXME: Use 'last visible' pos + P_Ent *old_bullet = P_EntFromKey(local_world->last_frame->prev, 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) @@ -2331,13 +2338,22 @@ void V_TickForever(WaveLaneCtx *lane) { if (bullet->is_bullet && bullet->has_hit) { + // FIXME: Use actual velocity + P_Ent *old_bullet = P_EntFromKey(local_frame->prev, 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 bullet_vel = SubVec2(bullet->bullet_end, bullet->bullet_start); + Vec2 hit_entry_velocity = bullet->hit_entry_velocity; - V_DrawLine(bullet->bullet_start, bullet->bullet_end, Color_Cyan); + V_DrawLine(start, end, Color_Cyan); ////////////////////////////// //- Wall particles @@ -2419,7 +2435,7 @@ void V_TickForever(WaveLaneCtx *lane) emitter.kind = V_ParticleKind_BloodTrail; // emitter.kind = V_ParticleKind_BloodDebris; - Vec2 dir = NormVec2(NegVec2(bullet_vel)); + Vec2 dir = NormVec2(NegVec2(hit_entry_velocity)); f32 angle = AngleFromVec2(dir); // f32 angle = 0; @@ -2453,267 +2469,6 @@ void V_TickForever(WaveLaneCtx *lane) - - - - // if (0) - // { - // for (P_Ent *bullet = P_FirstEnt(local_frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet)) - // { - // if (bullet->is_bullet && bullet->has_hit) - // { - // P_MaterialKind material = bullet->hit_material; - - // Vec2 hit_entry = bullet->hit_entry; - // Vec2 hit_entry_normal = bullet->hit_entry_normal; - // Vec2 bullet_vel = SubVec2(bullet->bullet_end, bullet->bullet_start); - - // V_DrawLine(bullet->bullet_start, bullet->bullet_end, Color_Cyan); - - // ////////////////////////////// - // //- Wall particles - - // if (material != P_MaterialKind_Flesh) - // { - // //- Wall debris - // { - // V_Emitter emitter = Zi; - // { - // // emitter.flags |= V_ParticleFlag_PruneWhenStill; - // // emitter.flags |= V_ParticleFlag_StainOnPrune; - - - // emitter.count = 4; - - // emitter.start = hit_entry; - // emitter.end = emitter.start; - - // emitter.color_lin = LinearFromSrgb(VEC4(0.4, 0.3, 0.2, 0.75)); - // emitter.color_spread = VEC4(0, 0, 0, 0.25); - - // emitter.speed = 2; - // // emitter.speed_spread = emitter.speed * 2; - // emitter.speed_spread = emitter.speed * 2.5; - - // emitter.velocity_falloff = 5; - // emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5; - - // Vec2 dir = hit_entry_normal; - - // emitter.angle = AngleFromVec2(dir); - // emitter.angle_spread = Tau * 0.5; - - // // 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.flags |= V_ParticleFlag_PruneWhenStill; - - // emitter.count = 32; - - // emitter.start = hit_entry; - // emitter.end = emitter.start; - - // emitter.color_lin = LinearFromSrgb(VEC4(0.5, 0.5, 0.5, 0.75)); - - // emitter.speed = 4; - // // emitter.speed_spread = emitter.speed * 2; - // emitter.speed_spread = emitter.speed * 2; - - // emitter.velocity_falloff = 12; - // emitter.velocity_falloff_spread = emitter.velocity_falloff_spread * 1.5; - - // Vec2 dir = hit_entry_normal; - - // emitter.angle = AngleFromVec2(dir); - // emitter.angle_spread = Tau * 0.1; - - // emitter.lifetime = 1; - // // emitter.lifetime = 0.05; - // // emitter.lifetime = 0.04; - // emitter.lifetime_spread = emitter.lifetime * 2; - // } - // V_PushParticles(emitter); - // } - // } - - // ////////////////////////////// - // //- Blood particles - - // if (material == P_MaterialKind_Flesh) - // { - // // Vec2 bullet_start = bullet->start; - // // Vec2 bullet_end = bullet->end; - - // V_ParticleFlag flags = 0; - // flags |= V_ParticleFlag_PruneWhenStill; - // flags |= V_ParticleFlag_StainOnPrune; - // if (TweakBool("Emitter stain trail", 1)) - // { - // flags |= V_ParticleFlag_StainTrail; - // } - // // f32 count = TweakFloat("Emitter count", 50, 0, 10000); - // f32 count = TweakFloat("Emitter count", 20, 1, 1000); - // f32 speed = TweakFloat("Emitter speed", 20, 0, 100); - // f32 falloff = TweakFloat("Emitter falloff", 50, 0, 100); - // f32 angle_spread = TweakFloat("Emitter angle spread", 0.1, 0, 1) * Tau; - - // V_Emitter emitter = Zi; - // { - // emitter.count = count; - // emitter.flags = flags; - - // // Vec2 dir = hit_entry_normal; - // Vec2 dir = NormVec2(NegVec2(bullet_vel)); - - // emitter.start = hit_entry; - // emitter.end = emitter.start; - - // emitter.speed = speed; - // emitter.speed_spread = speed * 2; - - // emitter.velocity_falloff = falloff; - // emitter.velocity_falloff_spread = falloff * 1.5; - - // emitter.angle = AngleFromVec2(dir); - // // emitter.angle_spread = Tau / 4; - // emitter.angle_spread = angle_spread; - // // emitter.angle_spread = Tau / 32; - - // // emitter.color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1)); - // emitter.color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 0.5)); - - // // emitter.color_spread = VEC4(0.1, 0, 0, 0); - // emitter.color_spread = VEC4(0.1, 0, 0, 0.5); - - // // emitter.color = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1)); - - // // emitter.angle_spread = 1; - // // emitter.angle_spread = 0.5; - // // emitter.angle_spread = Tau; - // } - // 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); - // } - // } - // } - - - - - - - - - - // { - // for (P_Ent *firer = P_FirstEnt(local_frame); firer->valid; firer = P_NextEnt(firer)) - // { - // if (firer->fire_held) - // { - // Affine firer_af = firer->af; - // P_Shape firer_world_shape = P_MulAffineShape(firer_af, firer->local_shape); - - // Vec2 ray_start = firer_world_shape.centroid; - // Vec2 ray_dir = firer->look; - - // // TODO: Real raycast query - // P_Ent *closest_victim = &P_NilEnt; - // P_RaycastResult victim_raycast = Zi; - // { - // f32 closest_len_sq = Inf; - // for (P_Ent *victim = P_FirstEnt(local_frame); victim->valid; victim = P_NextEnt(victim)) - // { - // if (victim != firer) - // { - // Affine victim_af = victim->af; - // P_Shape victim_world_shape = P_MulAffineShape(victim_af, victim->local_shape); - - // P_RaycastResult raycast = P_RaycastShape(victim_world_shape, ray_start, ray_dir); - // if (raycast.is_intersecting) - // { - // f32 len_sq = Vec2LenSq(SubVec2(raycast.p, ray_start)); - // if (len_sq < closest_len_sq) - // { - // closest_len_sq = len_sq; - // closest_victim = victim; - // victim_raycast = raycast; - // } - // } - // } - // } - // } - - // if (closest_victim->valid) - // { - // V_ParticleFlag flags = 0; - // flags |= V_ParticleFlag_PruneWhenStill; - // flags |= V_ParticleFlag_StainOnPrune; - // if (TweakBool("Emitter stain trail", 1)) - // { - // flags |= V_ParticleFlag_StainTrail; - // } - // // f32 count = TweakFloat("Emitter count", 50, 0, 10000); - // f32 count = TweakFloat("Emitter count", 50, 1, 1000); - // f32 speed = TweakFloat("Emitter speed", 20, 0, 100); - // f32 falloff = TweakFloat("Emitter falloff", 50, 0, 100); - // f32 angle_spread = TweakFloat("Emitter angle spread", 0.1, 0, 1) * Tau; - - // V_Emitter *emitter = V_PushEmitter(count); - // emitter->flags = flags; - - // Vec2 dir = victim_raycast.normal; - // emitter->start = victim_raycast.p; - // emitter->end = emitter->start; - - // emitter->speed = speed; - // emitter->speed_spread = speed * 2; - - // emitter->velocity_falloff = falloff; - // emitter->velocity_falloff_spread = falloff * 1.5; - - // emitter->angle = AngleFromVec2(dir); - // // emitter->angle_spread = Tau / 4; - // emitter->angle_spread = angle_spread; - // // emitter->angle_spread = Tau / 32; - - // emitter->color_lin = LinearFromSrgb(VEC4(0.5, 0.1, 0.1, 1)); - // emitter->color_spread = VEC4(0.1, 0, 0, 0); - - // // emitter->color = LinearFromSrgb(Vec4(0.5, 0.1, 0.1, 1)); - - // emitter->seed = RandU64FromState(&frame->rand); - // // emitter->angle_spread = 1; - // // emitter->angle_spread = 0.5; - // // emitter->angle_spread = Tau; - - // // V_DrawPoint(victim_raycast.p, Color_Green); - // // V_DrawLine(victim_raycast.p, AddVec2(victim_raycast.p, MulVec2(victim_raycast.normal, 0.5)), Color_White); - // } - - - // // for (P_QueryResult query = P_FirstRaycast(world, ray_start, ray_dir); query. - // // P_RaycastWorldResult hits = P_RaycastWorld(local_frame, ray_start, ray_dir) - // // { - // // } - // } - // } - // } - - - - ////////////////////////////// //- Push test emitter @@ -4949,6 +4704,11 @@ void V_TickForever(WaveLaneCtx *lane) frame->should_clear_particles = 1; } break; + case V_CmdKind_clear_bullets: + { + P_Msg *msg = P_PushMsg(P_MsgKind_ClearBullets, Zstr); + } break; + case V_CmdKind_test: { // V_PushNotif(Lit("Hello!!!")); diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index 50dc4f90..618b262d 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -19,6 +19,7 @@ X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \ X(reset_world, Reset world, V_CmdDescFlag_None, V_HOTKEY( Button_R, .ctrl = 1, .alt = 1 ), ) \ X(clear_particles, Clear particles, V_CmdDescFlag_None, V_HOTKEY( Button_C ), ) \ + X(clear_bullets, Clear bullets, V_CmdDescFlag_None, V_HOTKEY( Button_C, .shift = 1 ), ) \ X(test, Test, V_CmdDescFlag_None, V_HOTKEY( Button_Y ), ) \ /* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */