bomb ricochet

This commit is contained in:
jacob 2026-02-20 21:50:25 -06:00
parent 62776720c3
commit e6d842227f
7 changed files with 463 additions and 510 deletions

View File

@ -1151,16 +1151,10 @@ Vec2 MulXformVec2(Xform xf, Vec2 v)
Xform NormXform(Xform xf) Xform NormXform(Xform xf)
{ {
xf.r = NormRot(xf.r); xf.r = NormVec2(xf.r);
return xf; return xf;
} }
Vec2 NormRot(Vec2 r)
{
r = Vec2WithLen(r, 1);
return r;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Line //~ Line

View File

@ -525,7 +525,6 @@ Xform InvertXform(Xform xf);
Vec2 MulXformVec2(Xform xf, Vec2 v); Vec2 MulXformVec2(Xform xf, Vec2 v);
Xform NormXform(Xform xf); Xform NormXform(Xform xf);
Vec2 NormRot(Vec2 r);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Line //~ Line

View File

@ -70,6 +70,11 @@ P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b)
return (P_ConstraintKey) { .v = MixU64s(a, b) }; return (P_ConstraintKey) { .v = MixU64s(a, b) };
} }
P_EntKey P_EntKeyFromU64(u64 v)
{
return (P_EntKey) { .v = v };
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Rand helpers //~ Rand helpers
@ -1525,7 +1530,9 @@ void P_UniqueSpaceEntriesFromRay(Arena *arena, P_SpaceEntryList *result, i32 spa
Vec2I32 dda_pos = dda_start; Vec2I32 dda_pos = dda_start;
b32 done = 0; 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) 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; 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); EndScratch(scratch);
} }
@ -2241,7 +2254,7 @@ void P_StepFrame(P_Frame *frame)
weapon->is_weapon = 1; weapon->is_weapon = 1;
weapon->key = P_RandEntKey(); weapon->key = P_RandEntKey();
weapon->source = guy->key; weapon->source = guy->key;
// weapon->is_uzi = 1; // weapon->1is_uzi = 1;
weapon->is_launcher = 1; weapon->is_launcher = 1;
guy->weapon = weapon->key; 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) 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_time_ns = frame->time_ns;
// guy->last_roll_dir = NormRot(guy->control.move); // guy->last_roll_dir = NormVec2(guy->control.move);
guy->last_roll_dir = NormRot(guy->control.look); guy->last_roll_dir = NormVec2(guy->control.look);
} }
} }
} }
@ -2318,15 +2331,15 @@ void P_StepFrame(P_Frame *frame)
// Dampen movement // 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); f32 damp_force = TweakFloat("Guy damp force", 50, 0, 100);
Vec2 damp = MulVec2(NegVec2(guy->solved_v), damp_force * sim_dt); Vec2 damp = MulVec2(NegVec2(guy->v), damp_force * sim_dt);
guy->solved_v = AddVec2(guy->solved_v, damp); guy->v = AddVec2(guy->v, damp);
} }
else 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); b32 is_rolling = P_IsEntRolling(frame, guy);
if (is_rolling) if (is_rolling)
{ {
// Vec2 roll_dir = NormRot(guy->last_roll_dir); // Vec2 roll_dir = NormVec2(guy->last_roll_dir);
Vec2 roll_dir = NormRot(guy->xf.r); Vec2 roll_dir = NormVec2(guy->xf.r);
move = roll_dir; move = roll_dir;
// look = roll_dir; // look = roll_dir;
@ -2359,14 +2372,14 @@ void P_StepFrame(P_Frame *frame)
// Integrate linear movement // 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)); new_velocity = AddVec2(new_velocity, MulVec2(move, move_force * sim_dt));
if (Vec2Len(new_velocity) > max_speed) if (Vec2Len(new_velocity) > max_speed)
{ {
new_velocity = Vec2WithLen(new_velocity, max_speed); new_velocity = Vec2WithLen(new_velocity, max_speed);
} }
guy->solved_v = new_velocity; guy->v = new_velocity;
} }
// Integrate look // Integrate look
@ -2375,7 +2388,7 @@ void P_StepFrame(P_Frame *frame)
f32 desired_angle = AngleFromVec2(look); f32 desired_angle = AngleFromVec2(look);
f32 diff = UnwindAngleF32(desired_angle - cur_angle); f32 diff = UnwindAngleF32(desired_angle - cur_angle);
f32 look_force = 1.0 / (sim_dt * sim_dt) * turn_rate; 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 *ent0 = P_EntFromKey(frame, constraint->ent0);
P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
Vec2 v0 = ent0->solved_v; Vec2 v0 = ent0->v;
Vec2 v1 = ent1->solved_v; Vec2 v1 = ent1->v;
f32 w0 = ent0->solved_w; f32 w0 = ent0->w;
f32 w1 = ent1->solved_w; f32 w1 = ent1->w;
Vec2 normal = constraint->normal; Vec2 normal = constraint->normal;
Vec2 tangent = PerpVec2(normal); Vec2 tangent = PerpVec2(normal);
@ -2705,13 +2718,13 @@ void P_StepFrame(P_Frame *frame)
if (!P_IsEntNil(ent0)) if (!P_IsEntNil(ent0))
{ {
ent0->solved_v = v0; ent0->v = v0;
ent0->solved_w = w0; ent0->w = w0;
} }
if (!P_IsEntNil(ent1)) if (!P_IsEntNil(ent1))
{ {
ent1->solved_v = v1; ent1->v = v1;
ent1->solved_w = w1; ent1->w = w1;
} }
} }
} }
@ -2733,10 +2746,10 @@ void P_StepFrame(P_Frame *frame)
f32 inv_m1 = constraint->inv_m1; f32 inv_m1 = constraint->inv_m1;
f32 inv_i0 = constraint->inv_i0; f32 inv_i0 = constraint->inv_i0;
f32 inv_i1 = constraint->inv_i1; f32 inv_i1 = constraint->inv_i1;
Vec2 v0 = ent0->solved_v; Vec2 v0 = ent0->v;
Vec2 v1 = ent1->solved_v; Vec2 v1 = ent1->v;
f32 w0 = ent0->solved_w; f32 w0 = ent0->w;
f32 w1 = ent1->solved_w; f32 w1 = ent1->w;
Vec2 center0 = constraint->static_center0; Vec2 center0 = constraint->static_center0;
Vec2 center1 = constraint->static_center1; Vec2 center1 = constraint->static_center1;
@ -2837,13 +2850,13 @@ void P_StepFrame(P_Frame *frame)
if (!P_IsEntNil(ent0)) if (!P_IsEntNil(ent0))
{ {
ent0->solved_v = v0; ent0->v = v0;
ent0->solved_w = w0; ent0->w = w0;
} }
if (!P_IsEntNil(ent1)) if (!P_IsEntNil(ent1))
{ {
ent1->solved_v = v1; ent1->v = v1;
ent1->solved_w = w1; ent1->w = w1;
} }
} }
@ -2876,13 +2889,13 @@ void P_StepFrame(P_Frame *frame)
if (!P_IsEntNil(ent0)) if (!P_IsEntNil(ent0))
{ {
ent0->solved_v = v0; ent0->v = v0;
ent0->solved_w = w0; ent0->w = w0;
} }
if (!P_IsEntNil(ent1)) if (!P_IsEntNil(ent1))
{ {
ent1->solved_v = v1; ent1->v = v1;
ent1->solved_w = w1; 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)) 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; Xform xf = ent->xf;
xf.t = AddVec2(xf.t, MulVec2(ent->solved_v, solver_dt)); xf.t = AddVec2(xf.t, MulVec2(ent->v, solver_dt));
xf.r = RotateVec2Angle(xf.r, ent->solved_w * solver_dt); xf.r = RotateVec2Angle(xf.r, ent->w * solver_dt);
ent->xf = xf; ent->xf = xf;
} }
} }
@ -2908,7 +2921,19 @@ void P_StepFrame(P_Frame *frame)
P_Space post_solve_ents_space = P_SpaceFromEnts(scratch.arena, 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)) for (P_Ent *firer = P_FirstEnt(frame); !P_IsEntNil(firer); firer = P_NextEnt(firer))
{ {
P_Ent *weapon = P_EntFromKey(frame, firer->weapon); 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_held || firer->control.fire_presses))
// if (weapon->is_weapon && 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 fire_rate = 50;
f32 bullets_per_fire = 1; 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_pos = Zi;
Vec2 fire_dir = Zi; Vec2 fire_dir = Zi;
Vec2 fire_base0 = Zi; Vec2 fire_base0 = Zi;
Vec2 fire_base1 = Zi; Vec2 fire_base1 = Zi;
if (can_fire)
{ {
Vec2 look = firer->control.look; Vec2 look = firer->control.look;
P_Anim anim = P_AnimFromEnt(frame, firer); 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]; SPR_Ray fire_ray = wep.rays[SPR_RayKind_Ap];
fire_pos = MulAffineVec2(wep_pix_to_world_af, fire_ray.pos); 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 = MulVec2(PerpVec2(NegVec2(firer->xf.r)), WedgeVec2(firer->xf.r, SubVec2(firer->xf.t, fire_pos)));
fire_base0 = AddVec2(fire_base0, firer->xf.t); 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_base0, Color_Yellow);
P_DebugDrawPoint(fire_base1, Color_Green); P_DebugDrawPoint(fire_base1, Color_Green);
P_DebugDrawPoint(fire_pos, Color_Red); 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 // 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 // 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 // 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 // Firer origin -> weapon chamber path
BulletPath *path = PushStruct(scratch.arena, BulletPath); BulletPath *path = PushStruct(scratch.arena, BulletPath);
SllQueuePush(first_bullet_path, last_bullet_path, path); SllQueuePush(first_bullet_path, last_bullet_path, path);
path->start = bullet->bullet_base0; path->start = fire_base0;
path->end = bullet->bullet_base1; path->end = fire_base1;
} }
{ {
// Weapon chamber -> bullet start path // Weapon chamber -> bullet start path
BulletPath *path = PushStruct(scratch.arena, BulletPath); BulletPath *path = PushStruct(scratch.arena, BulletPath);
SllQueuePush(first_bullet_path, last_bullet_path, path); SllQueuePush(first_bullet_path, last_bullet_path, path);
path->start = bullet->bullet_base1; path->start = fire_base1;
path->end = bullet->bullet_start; 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); BulletPath *path = PushStruct(scratch.arena, BulletPath);
SllQueuePush(first_bullet_path, last_bullet_path, path); SllQueuePush(first_bullet_path, last_bullet_path, path);
path->start = bullet->bullet_start; path->start = p0;
path->end = bullet->bullet_end; path->end = p1;
} }
P_EntKey victim_key = Zi; P_EntKey victim_key = Zi;
@ -3218,7 +3194,7 @@ void P_StepFrame(P_Frame *frame)
{ {
P_SpaceEntry *entry = &entry_node->entry; P_SpaceEntry *entry = &entry_node->entry;
P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; 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_Shape potential_victim_shape = entry->shape;
P_RaycastResult entrance_raycast = P_RaycastShape(potential_victim_shape, path->start, path_dir); 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); P_Ent *victim = P_EntFromKey(frame, victim_key);
// TODO: Truncate bullet trail bullet->has_hit = 0;
Vec2 final_pos = p1;
if (hit) if (hit)
{ {
Vec2 normal = victim_raycast.normal;
bullet->has_hit = 1; bullet->has_hit = 1;
bullet->hit_entry = victim_raycast.p; 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->bullet_end = bullet->hit_entry;
bullet->exists = 0;
if (victim->is_guy) if (victim->is_guy)
{ {
bullet->hit_material = P_MaterialKind_Flesh; bullet->hit_material = P_MaterialKind_Flesh;
@ -3263,14 +3246,35 @@ void P_StepFrame(P_Frame *frame)
{ {
bullet->hit_material = P_MaterialKind_Wall; 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 // TODO: Remove this
if (!P_IsEntNil(victim)) 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; victim->health -= 0.25;
} }
@ -3280,8 +3284,8 @@ void P_StepFrame(P_Frame *frame)
bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2); bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2);
bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2); bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2);
if ( if (
bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y || bullet->xf.t.x < bounds.p0.x || bullet->xf.t.y < bounds.p0.y ||
bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y bullet->xf.t.x > bounds.p1.x || bullet->xf.t.y > bounds.p1.y
) )
{ {
bullet->exists = 0; 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 //- Kill guys

View File

@ -129,14 +129,11 @@ Struct(P_Ent)
//- Bullet / hit //- Bullet / hit
b32 is_bullet; b32 is_bullet;
Vec2 bullet_base0;
Vec2 bullet_base1;
Vec2 bullet_start;
Vec2 bullet_end;
b32 has_hit; b32 has_hit;
Vec2 hit_entry; Vec2 hit_entry;
Vec2 hit_entry_normal; Vec2 hit_entry_normal;
Vec2 hit_entry_velocity;
P_MaterialKind hit_material; P_MaterialKind hit_material;
//- Bomb //- Bomb
@ -189,8 +186,8 @@ Struct(P_Ent)
//- Solver //- Solver
Vec2 solved_v; Vec2 v; // Linear velocity
f32 solved_w; f32 w; // Angular velocity
}; };
Struct(P_EntListNode) Struct(P_EntListNode)
@ -417,6 +414,7 @@ Enum(P_MsgKind)
// Client -> Server // Client -> Server
P_MsgKind_SaveWorld, P_MsgKind_SaveWorld,
P_MsgKind_ResetWorld, P_MsgKind_ResetWorld,
P_MsgKind_ClearBullets,
P_MsgKind_Teleport, P_MsgKind_Teleport,
P_MsgKind_TileEdit, P_MsgKind_TileEdit,
P_MsgKind_Prefab, P_MsgKind_Prefab,
@ -586,6 +584,7 @@ b32 P_IsFrameNil(P_Frame *frame);
b32 P_MatchEntKey(P_EntKey a, P_EntKey b); b32 P_MatchEntKey(P_EntKey a, P_EntKey b);
b32 P_MatchConstraintKey(P_ConstraintKey a, P_ConstraintKey b); b32 P_MatchConstraintKey(P_ConstraintKey a, P_ConstraintKey b);
P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b); P_ConstraintKey P_ConstraintKeyFromU64s(u64 a, u64 b);
P_EntKey P_EntKeyFromU64(u64 v);
#define P_FmtKey(key) FmtHandle((key).v) #define P_FmtKey(key) FmtHandle((key).v)

View File

@ -557,6 +557,18 @@ void S_TickForever(WaveLaneCtx *lane)
} }
} }
} break; } 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 //- Teleport
case P_MsgKind_Teleport: case P_MsgKind_Teleport:
{ {

View File

@ -914,8 +914,8 @@ void V_TickForever(WaveLaneCtx *lane)
Vec2 look_delta = mouse_delta; Vec2 look_delta = mouse_delta;
look_delta = MulVec2(look_delta, mouse_sensitivity * mouse_scale_factor); 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_crosshair_rot = NormVec2(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base));
Vec2 prev_look_rot = NormRot(prev_frame->look); Vec2 prev_look_rot = NormVec2(prev_frame->look);
Vec2 rot = RotateVec2(prev_look_rot, InvertRot(prev_crosshair_rot)); Vec2 rot = RotateVec2(prev_look_rot, InvertRot(prev_crosshair_rot));
look_delta = RotateVec2(look_delta, 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)); 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); look_ratio = VEC2(0.5, 0.5);
} }
Vec2 prev_crosshair_rot = NormRot(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base)); Vec2 prev_crosshair_rot = NormVec2(SubVec2(prev_frame->world_crosshair, prev_frame->world_crosshair_base));
Vec2 prev_look_rot = NormRot(prev_frame->look); Vec2 prev_look_rot = NormVec2(prev_frame->look);
Vec2 rot = RotateVec2(prev_look_rot, InvertRot(prev_crosshair_rot)); Vec2 rot = RotateVec2(prev_look_rot, InvertRot(prev_crosshair_rot));
Vec2 look_pos = frame->look; Vec2 look_pos = frame->look;
@ -1840,9 +1838,12 @@ void V_TickForever(WaveLaneCtx *lane)
} }
local_world->seed = predict_world->seed; 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); 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); P_DebugDrawFrame(local_frame);
} }
@ -1901,7 +1902,7 @@ void V_TickForever(WaveLaneCtx *lane)
//- Compute sprite transforms //- Compute sprite transforms
Affine cur_ent_to_world_af = MulAffineXform(AffineIdentity, ent->xf); 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_body_pix_to_world_af = AffineIdentity;
Affine cur_wep_pix_to_world_af = AffineIdentity; Affine cur_wep_pix_to_world_af = AffineIdentity;
Affine desired_body_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]; 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_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_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); 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_guy_origin = desired_ent_to_world_af.og;
frame->world_crosshair_base = desired_crosshair_base; 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_start = cur_fire_pos;
Vec2 line_end = AddVec2(line_start, cur_fire_dir); Vec2 line_end = AddVec2(line_start, cur_fire_dir);
@ -2263,8 +2264,14 @@ void V_TickForever(WaveLaneCtx *lane)
{ {
if (bullet->is_bullet) if (bullet->is_bullet)
{ {
Vec2 start = bullet->bullet_start; // FIXME: Use 'last visible' pos
Vec2 end = bullet->bullet_end; 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; b32 skip = 0;
if (bullet->has_hit) if (bullet->has_hit)
@ -2331,13 +2338,22 @@ void V_TickForever(WaveLaneCtx *lane)
{ {
if (bullet->is_bullet && bullet->has_hit) 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; P_MaterialKind material = bullet->hit_material;
Vec2 hit_entry = bullet->hit_entry; Vec2 hit_entry = bullet->hit_entry;
Vec2 hit_entry_normal = bullet->hit_entry_normal; 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 //- Wall particles
@ -2419,7 +2435,7 @@ void V_TickForever(WaveLaneCtx *lane)
emitter.kind = V_ParticleKind_BloodTrail; emitter.kind = V_ParticleKind_BloodTrail;
// emitter.kind = V_ParticleKind_BloodDebris; // emitter.kind = V_ParticleKind_BloodDebris;
Vec2 dir = NormVec2(NegVec2(bullet_vel)); Vec2 dir = NormVec2(NegVec2(hit_entry_velocity));
f32 angle = AngleFromVec2(dir); f32 angle = AngleFromVec2(dir);
// f32 angle = 0; // 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 //- Push test emitter
@ -4949,6 +4704,11 @@ void V_TickForever(WaveLaneCtx *lane)
frame->should_clear_particles = 1; frame->should_clear_particles = 1;
} break; } break;
case V_CmdKind_clear_bullets:
{
P_Msg *msg = P_PushMsg(P_MsgKind_ClearBullets, Zstr);
} break;
case V_CmdKind_test: case V_CmdKind_test:
{ {
// V_PushNotif(Lit("Hello!!!")); // V_PushNotif(Lit("Hello!!!"));

View File

@ -19,6 +19,7 @@
X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \ 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(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_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 ), ) \ X(test, Test, V_CmdDescFlag_None, V_HOTKEY( Button_Y ), ) \
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */