diff --git a/src/base/base_crum.c b/src/base/base_crum.c index bc5b4436..28c80c2b 100644 --- a/src/base/base_crum.c +++ b/src/base/base_crum.c @@ -145,65 +145,76 @@ f64 CR_FloatFromString(String str) } } - // Find decimal place - u64 frac_start_idx = whole_start_idx; - for (; ok && frac_start_idx < str.len; ++frac_start_idx) + String whole_str = STRING(str.len - whole_start_idx, &str.text[whole_start_idx]); + if (MatchString(whole_str, Lit("inf"))) { - u8 c = str.text[frac_start_idx]; - if (c == '.') - { - break; - } + result = Inf * sign; } - - // Parse whole part - u64 whole_part = 0; - for (u64 char_idx = whole_start_idx; ok && char_idx < frac_start_idx; ++char_idx) + else if (MatchString(whole_str, Lit("NaN"))) { - u8 c = str.text[char_idx]; - if (c >= '0' && c <= '9') - { - u8 digit = c - '0'; - whole_part += digit * PowU64(10, frac_start_idx - (char_idx + 1)); - } - else - { - ok = 0; - } - } - - // Parse frac part - u64 frac_part = 0; - for (u64 char_idx = frac_start_idx + 1; ok && char_idx < str.len; ++char_idx) - { - u8 c = str.text[char_idx]; - if (c >= '0' && c <= '9') - { - u8 digit = c - '0'; - frac_part += digit * PowU64(10, str.len - (char_idx + 1)); - } - else - { - ok = 0; - } - } - - if (ok) - { - if (frac_part != 0) - { - result = ((f64)whole_part + ((f64)frac_part / PowU64(10, str.len - (frac_start_idx + 1)))) * sign; - } - else - { - result = (f64)whole_part * sign; - } + result = Nan; } else { - result = 0; - } + // Find decimal place + u64 frac_start_idx = whole_start_idx; + for (; ok && frac_start_idx < str.len; ++frac_start_idx) + { + u8 c = str.text[frac_start_idx]; + if (c == '.') + { + break; + } + } + // Parse whole part + u64 whole_part = 0; + for (u64 char_idx = whole_start_idx; ok && char_idx < frac_start_idx; ++char_idx) + { + u8 c = str.text[char_idx]; + if (c >= '0' && c <= '9') + { + u8 digit = c - '0'; + whole_part += digit * PowU64(10, frac_start_idx - (char_idx + 1)); + } + else + { + ok = 0; + } + } + + // Parse frac part + u64 frac_part = 0; + for (u64 char_idx = frac_start_idx + 1; ok && char_idx < str.len; ++char_idx) + { + u8 c = str.text[char_idx]; + if (c >= '0' && c <= '9') + { + u8 digit = c - '0'; + frac_part += digit * PowU64(10, str.len - (char_idx + 1)); + } + else + { + ok = 0; + } + } + + if (ok) + { + if (frac_part != 0) + { + result = ((f64)whole_part + ((f64)frac_part / PowU64(10, str.len - (frac_start_idx + 1)))) * sign; + } + else + { + result = (f64)whole_part * sign; + } + } + else + { + result = 0; + } + } return result; } diff --git a/src/pp/pp.c b/src/pp/pp.c index 271f0b1c..1a5015f4 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -3184,137 +3184,140 @@ 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_EntList hits_to_spawn = Zi; + for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(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; - // f32 spread = 0; - - b32 should_ricochet = 0; - - f32 initial_speed = 1; - f32 speed_falloff = 0; - if (weapon->is_uzi) + if (bullet->is_bullet) { - 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; - } + 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); - ////////////////////////////// - //- Initialize + b32 is_first_bullet_tick = bullet->created_at_tick == frame->tick; - // 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 properties - Struct(BulletPath) - { - BulletPath *next; - Vec2 start; - Vec2 end; - }; - BulletPath *first_bullet_path = 0; - BulletPath *last_bullet_path = 0; + f32 spread = Tau * 0.05; + // f32 spread = Tau * 0.01; + // f32 spread = 0; - if (is_first_bullet_tick) - { - Vec2 fire_pos = Zi; - Vec2 fire_dir = Zi; - Vec2 fire_base0 = Zi; - Vec2 fire_base1 = Zi; + b32 should_ricochet = 0; + + f32 initial_speed = 1; + f32 speed_falloff = 0; + if (weapon->is_uzi) { - Vec2 look = firer->control.look; - P_Anim anim = P_AnimFromEnt(frame, firer); - SPR_Sprite body = SPR_SpriteFromSheet(anim.sheet, anim.span, anim.frame_seq); - SPR_Sprite wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq); + 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; + } - //- Compute sprite transforms - Affine ent_to_world_af = MulAffineXform(AffineIdentity, firer->xf); - Affine body_pix_to_world_af = AffineIdentity; - Affine wep_pix_to_world_af = AffineIdentity; + ////////////////////////////// + //- 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; { - Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter); + Vec2 look = firer->control.look; + P_Anim anim = P_AnimFromEnt(frame, firer); + SPR_Sprite body = SPR_SpriteFromSheet(anim.sheet, anim.span, anim.frame_seq); + SPR_Sprite wep = SPR_SpriteFromSheet(anim.wep_sheet, anim.span, anim.frame_seq); - //- Compute body transform - Affine body_pix_to_ent_af = AffineIdentity; + //- Compute sprite transforms + Affine ent_to_world_af = MulAffineXform(AffineIdentity, firer->xf); + Affine body_pix_to_world_af = AffineIdentity; + Affine wep_pix_to_world_af = AffineIdentity; { - body_pix_to_ent_af = ScaleAffine(body_pix_to_ent_af, pix_scale); + Vec2 pix_scale = VEC2(1.0 / P_CellsPerMeter, 1.0 / P_CellsPerMeter); - SPR_Ray anchor_ray = body.rays[SPR_RayKind_Anchor]; - body_pix_to_ent_af = RotateAffine(body_pix_to_ent_af, InvertRot(anchor_ray.dir)); - body_pix_to_ent_af = TranslateAffine(body_pix_to_ent_af, NegVec2(anchor_ray.pos)); + //- Compute body transform + Affine body_pix_to_ent_af = AffineIdentity; + { + body_pix_to_ent_af = ScaleAffine(body_pix_to_ent_af, pix_scale); + + SPR_Ray anchor_ray = body.rays[SPR_RayKind_Anchor]; + body_pix_to_ent_af = RotateAffine(body_pix_to_ent_af, InvertRot(anchor_ray.dir)); + body_pix_to_ent_af = TranslateAffine(body_pix_to_ent_af, NegVec2(anchor_ray.pos)); + } + + //- Compute weapon transform + Affine wep_pix_to_ent_af = AffineIdentity; + { + wep_pix_to_ent_af = ScaleAffine(wep_pix_to_ent_af, pix_scale); + + SPR_Ray body_anchor_ray = body.rays[SPR_RayKind_Anchor]; + SPR_Ray body_ap_ray = body.rays[SPR_RayKind_Ap]; + wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, InvertRot(body_anchor_ray.dir)); + wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, SubVec2(body_ap_ray.pos, body_anchor_ray.pos)); + wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, NegVec2(body_ap_ray.dir)); + + SPR_Ray anchor_ray = wep.rays[SPR_RayKind_Anchor]; + wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, anchor_ray.dir); + wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, NegVec2(anchor_ray.pos)); + } + + body_pix_to_world_af = MulAffine(ent_to_world_af, body_pix_to_ent_af); + wep_pix_to_world_af = MulAffine(ent_to_world_af, wep_pix_to_ent_af); } - //- Compute weapon transform - Affine wep_pix_to_ent_af = AffineIdentity; - { - wep_pix_to_ent_af = ScaleAffine(wep_pix_to_ent_af, pix_scale); + SPR_Ray muzzle_ray = wep.rays[SPR_RayKind_Ap]; + fire_pos = MulAffineVec2(wep_pix_to_world_af, muzzle_ray.pos); + fire_dir = NormVec2(MulAffineBasisVec2(wep_pix_to_world_af, muzzle_ray.dir)); - SPR_Ray body_anchor_ray = body.rays[SPR_RayKind_Anchor]; - SPR_Ray body_ap_ray = body.rays[SPR_RayKind_Ap]; - wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, InvertRot(body_anchor_ray.dir)); - wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, SubVec2(body_ap_ray.pos, body_anchor_ray.pos)); - wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, NegVec2(body_ap_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); - SPR_Ray anchor_ray = wep.rays[SPR_RayKind_Anchor]; - wep_pix_to_ent_af = RotateAffine(wep_pix_to_ent_af, anchor_ray.dir); - wep_pix_to_ent_af = TranslateAffine(wep_pix_to_ent_af, NegVec2(anchor_ray.pos)); - } + Vec2 chamber_pos = MulAffineVec2(body_pix_to_world_af, body.rays[SPR_RayKind_Ap].pos); - body_pix_to_world_af = MulAffine(ent_to_world_af, body_pix_to_ent_af); - wep_pix_to_world_af = MulAffine(ent_to_world_af, wep_pix_to_ent_af); + fire_base1 = MulVec2(PerpVec2(fire_dir), WedgeVec2(fire_dir, SubVec2(fire_pos, chamber_pos))); + fire_base1 = AddVec2(fire_base1, chamber_pos); + + P_DebugDrawLine(fire_base0, fire_base1, Color_Yellow); + P_DebugDrawLine(fire_base1, fire_pos, Color_Green); + P_DebugDrawPoint(fire_base0, Color_Yellow); + P_DebugDrawPoint(fire_base1, Color_Green); + P_DebugDrawPoint(fire_pos, Color_Red); + + Vec2 bullet_dir = RotateVec2Angle(fire_dir, spread * (Norm24(MixU64s(bullet->key.v, P_BulletSpreadBasis)) - 0.5)); + + bullet->xf.t = fire_pos; + bullet->xf.r = NormVec2(bullet_dir); + + bullet->v = MulVec2(NormVec2(bullet_dir), initial_speed); + bullet->v = AddVec2(bullet->v, firer->v); } - SPR_Ray muzzle_ray = wep.rays[SPR_RayKind_Ap]; - fire_pos = MulAffineVec2(wep_pix_to_world_af, muzzle_ray.pos); - fire_dir = NormVec2(MulAffineBasisVec2(wep_pix_to_world_af, muzzle_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); - - Vec2 chamber_pos = MulAffineVec2(body_pix_to_world_af, body.rays[SPR_RayKind_Ap].pos); - - fire_base1 = MulVec2(PerpVec2(fire_dir), WedgeVec2(fire_dir, SubVec2(fire_pos, chamber_pos))); - fire_base1 = AddVec2(fire_base1, chamber_pos); - - P_DebugDrawLine(fire_base0, fire_base1, Color_Yellow); - P_DebugDrawLine(fire_base1, fire_pos, Color_Green); - P_DebugDrawPoint(fire_base0, Color_Yellow); - P_DebugDrawPoint(fire_base1, Color_Green); - P_DebugDrawPoint(fire_pos, Color_Red); - - Vec2 bullet_dir = RotateVec2Angle(fire_dir, spread * (Norm24(MixU64s(bullet->key.v, P_BulletSpreadBasis)) - 0.5)); - - bullet->xf.t = fire_pos; - bullet->xf.r = NormVec2(bullet_dir); - - bullet->v = MulVec2(NormVec2(bullet_dir), initial_speed); - bullet->v = AddVec2(bullet->v, firer->v); - } @@ -3322,168 +3325,171 @@ void P_StepFrame(P_Frame *frame) - - // 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 = 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 = 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 = p0; - path->end = p1; - } - - 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) + // 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 { - P_SpaceEntry *entry = &entry_node->entry; - P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; - P_Ent *potential_victim = P_EntFromKey(frame, potential_victim_key); - b32 can_hit = ( - P_IsEntNil(potential_victim) || - (potential_victim->is_guy && (!P_MatchEntKey(potential_victim->key, firer->key) || P_IsEntKeyNil(firer->key))) + // Firer origin -> weapon chamber path + BulletPath *path = PushStruct(scratch.arena, BulletPath); + SllQueuePush(first_bullet_path, last_bullet_path, path); + 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 = 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 = p0; + path->end = p1; + } + + P_EntKey victim_key = Zi; + P_RaycastResult victim_raycast = Zi; + b32 has_hit = 0; + { + for (BulletPath *path = first_bullet_path; path && !has_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 ); - if (can_hit) + f32 closest_len_sq = Inf; + for (P_SpaceEntryNode *entry_node = cast_entries.first; entry_node; entry_node = entry_node->next) { - 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_SpaceEntry *entry = &entry_node->entry; + P_EntKey potential_victim_key = (P_EntKey) { .v = entry->shape_id }; + P_Ent *potential_victim = P_EntFromKey(frame, potential_victim_key); + b32 can_hit = ( + P_IsEntNil(potential_victim) || + (potential_victim->is_guy && (!P_MatchEntKey(potential_victim->key, firer->key) || P_IsEntKeyNil(firer->key))) + ); + if (can_hit) { - 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)) + 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) { - f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, path->start)); - if (len_sq < closest_len_sq) + 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)) { - closest_len_sq = len_sq; - victim_key = potential_victim_key; - victim_raycast = entrance_raycast; - hit = 1; + 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; + has_hit = 1; + } } } } } } } - } - P_Ent *victim = P_EntFromKey(frame, victim_key); + P_Ent *victim = P_EntFromKey(frame, victim_key); - 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 = normal; - - // FIXME: Use relative velocity at collision point - bullet->hit_entry_velocity = bullet->v; - - // bullet->bullet_end = bullet->hit_entry; - if (victim->is_guy) + // Create hit + Vec2 final_pos = p1; + if (has_hit) { - bullet->hit_material = P_MaterialKind_Flesh; - } - else - { - bullet->hit_material = P_MaterialKind_Wall; + Vec2 normal = victim_raycast.normal; + bullet->bullet_hits_count += 1; + + P_Ent *hit = P_PushTempEnt(scratch.arena, &hits_to_spawn); + { + 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 + hit->hit_entry_velocity = bullet->v; + if (victim->is_guy) + { + hit->hit_material = P_MaterialKind_Flesh; + } + else + { + hit->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(hit->hit_entry, MulVec2(hit->hit_entry_normal, collision_offset)); } - // Reflect velocity along normal - if (should_ricochet) + bullet->xf.t = final_pos; + if (Vec2LenSq(dir) > (0.001 * 0.001)) { - bullet->v = SubVec2(bullet->v, MulVec2(normal, 2 * DotVec2(bullet->v, normal))); - bullet->v = MulVec2(bullet->v, 0.5); + bullet->xf.r = NormVec2(dir); } - else + bullet->v = MulVec2(bullet->v, 1.0 - SaturateF32(speed_falloff * sim_dt)); + + // TODO: Remove this + if (!is_client && !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->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; } - - 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 (!is_client && !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->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; } } + P_SpawnEntsFromList(frame, hits_to_spawn); } diff --git a/src/pp/pp.h b/src/pp/pp.h index d05eda38..4278ee8b 100644 --- a/src/pp/pp.h +++ b/src/pp/pp.h @@ -8,6 +8,7 @@ #define P_WallShapeIDBasis 0x40d501b4cf6d4f0cull #define P_BulletSpreadBasis 0xc3b72fe38ca5a1d6ull +#define P_BulletHitBasis 0xbc70fc783c1c507full Struct(P_EntKey) { @@ -84,7 +85,7 @@ Struct(P_DebugDrawNode) #define P_RollTimeNs NsFromSeconds(0.5) #define P_RollTurnTimeNs (NsFromSeconds(0.1)) #define P_RollTimeoutNs NsFromSeconds(0.5) -#define P_ObservationLifetimeNs NsFromSeconds(2) +#define P_ObservationDurationSeconds 1 Struct(P_Control) { @@ -121,8 +122,9 @@ Struct(P_Ent) //- Client data - i64 observation_time_ns; - b32 has_observed; + i64 initial_observation_time_ns; + i64 last_observation_time_ns; + b32 is_first_observation; //- Build data @@ -137,11 +139,14 @@ Struct(P_Ent) Xform xf; - //- Bullet / hit + //- Bullet b32 is_bullet; + u32 bullet_hits_count; - b32 has_hit; + //- Hit + + b32 is_hit; Vec2 hit_entry; Vec2 hit_entry_normal; Vec2 hit_entry_velocity; diff --git a/src/pp/pp_transcode.c b/src/pp/pp_transcode.c index 8f1c96b6..55af2bdf 100644 --- a/src/pp/pp_transcode.c +++ b/src/pp/pp_transcode.c @@ -23,7 +23,7 @@ String P_PackWorld(Arena *arena, P_World *src_world) { // TODO: Pack ignored ents 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; } diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 61dd3113..23776930 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -1895,14 +1895,16 @@ void V_TickForever(WaveLaneCtx *lane) ////////////////////////////// //- Observe entities + + 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; + i64 expiration_ns = NsFromSeconds(P_ObservationDurationSeconds); + V_Observation *obs = 0; { 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) { if (obs->key == key.v) @@ -1910,19 +1912,20 @@ void V_TickForever(WaveLaneCtx *lane) break; } } + 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 - DllQueueRemove(V.first_observation, V.last_observation, obs); - DllQueuePush(V.first_observation, V.last_observation, obs); - obs->time_ns = frame->time_ns; + // Observation expired, re-init + obs->initial_observation_time_ns = frame->time_ns; } + DllQueueRemove(V.first_observation, V.last_observation, obs); + DllQueuePush(V.first_observation, V.last_observation, obs); } 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 obs = V.first_observation; @@ -1935,14 +1938,14 @@ void V_TickForever(WaveLaneCtx *lane) obs = PushStruct(perm, V_Observation); } 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); 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->has_observed = observation_time_ns < frame->time_ns; + ent->last_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 @@ -2566,24 +2550,15 @@ void V_TickForever(WaveLaneCtx *lane) // 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_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 = hit->hit_material; - 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; + Vec2 hit_entry = hit->hit_entry; + Vec2 hit_entry_normal = hit->hit_entry_normal; + Vec2 hit_entry_velocity = hit->hit_entry_velocity; ////////////////////////////// //- 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 diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index 5e27e14d..277844d2 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -239,7 +239,8 @@ Struct(V_Observation) V_Observation *next_in_bin; V_Observation *prev_in_bin; u64 key; - i64 time_ns; + i64 initial_observation_time_ns; + i64 last_observation_time_ns; }; Struct(V_ObservationBin)