mirror of
https://github.com/vladtrc/iw4L
synced 2026-10-26 02:11:39 -04:00
Let shotgun pellets break and push props such as cash registers
Bullets reach the client as bullet hit events, which on_entity_bullet_hit turns into dynent damage and impulses. Shotgun pellets (and turret rounds) arrive as pellet records instead, and drain_pellet_fx only drew their tracers and impacts, so a shotgun never broke a cash register, a suitcase or a card reader, and never pushed a prop. Each pellet segment that ends on a surface now strikes the nearest dynent on it the way a bullet does, from the authoritative copy only: drain_pellet_fx sends a DynEntPelletHit, and hit_dyn_ents_from_pellets finds the dynent on the segment and damages and pushes it with the weapon's hit damage. Checked on the Mac on mp_terminal, two short AA-12 bursts at the food-court register: before, the register and card readers stayed whole; after, both broke. Reported from an iPhone match where the AA-12's pellets kicked up dust on suitcases that a SCAR then burst open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,14 @@ pub struct DynEntWakeBroadphase {
|
||||
pub query_ms: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Message)]
|
||||
pub struct DynEntPelletHit {
|
||||
pub world: frame::WorldStamp,
|
||||
pub weapon: u32,
|
||||
pub start: Vec3,
|
||||
pub end: Vec3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct DynEntWakeEntry {
|
||||
entity: Entity,
|
||||
@@ -787,6 +795,59 @@ pub(crate) fn on_entity_bullet_hit(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn hit_dyn_ents_from_pellets(
|
||||
mut hits: MessageReader<DynEntPelletHit>,
|
||||
weapons: Option<Res<PreparedWeapons>>,
|
||||
catalog: Option<Res<asset_world::MapXModelSceneCatalog>>,
|
||||
mut broadphase: ResMut<DynEntWakeBroadphase>,
|
||||
mut instances: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut WorldDynEntInstance,
|
||||
&Transform,
|
||||
&mut Visibility,
|
||||
),
|
||||
With<DynEntModelEntity>,
|
||||
>,
|
||||
mut impulses: MessageWriter<DynEntPhysImpulse>,
|
||||
mut phys: ResMut<DynEntPhysWorld>,
|
||||
mut host: Option<ResMut<HostFxSystem>>,
|
||||
fx: Option<Res<PreparedFxCatalog>>,
|
||||
scene: Option<Res<WorldScene>>,
|
||||
entity_marks: Res<EntityMarks>,
|
||||
) {
|
||||
let catalog = catalog.as_deref();
|
||||
let fx = fx.as_deref();
|
||||
let scene = scene.as_deref();
|
||||
let marks = entity_marks.as_ref();
|
||||
for hit in hits.read() {
|
||||
let Some((entity, pose, bullet_scale)) =
|
||||
closest_segment_hit(hit.start, hit.end, catalog, &mut broadphase, &instances)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let weapons = weapons
|
||||
.as_deref()
|
||||
.and_then(|weapons| weapons.for_event(hit.world).ok());
|
||||
hit_one_dyn_ent(
|
||||
entity,
|
||||
pose,
|
||||
hit.start,
|
||||
hit.end,
|
||||
bullet_scale,
|
||||
weapon_hit_damage(weapons.as_ref(), hit.weapon),
|
||||
&mut instances,
|
||||
&mut impulses,
|
||||
phys.as_mut(),
|
||||
host.as_deref_mut(),
|
||||
fx,
|
||||
scene,
|
||||
marks,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_entity_event_sound(
|
||||
sound: On<EntityEventSound>,
|
||||
weapons: Option<Res<PreparedWeapons>>,
|
||||
@@ -917,6 +978,7 @@ fn on_physics_sphere(
|
||||
|
||||
pub fn register_dyn_ent_wake(app: &mut App) {
|
||||
app.scoped::<DynEntWakeBroadphase>(frame::MatchScope::Live)
|
||||
.scoped_message::<DynEntPelletHit>(frame::MatchScope::Live)
|
||||
.add_observer(on_entity_explosion)
|
||||
.add_observer(on_physics_sphere)
|
||||
.add_observer(on_entity_bullet_hit)
|
||||
@@ -927,6 +989,12 @@ pub fn register_dyn_ent_wake(app: &mut App) {
|
||||
.in_set(frame::InMatch)
|
||||
.in_set(net::ClientSet::Receive),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
hit_dyn_ents_from_pellets
|
||||
.in_set(frame::InMatch)
|
||||
.in_set(net::ClientSet::Present),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
wake_player_overlap
|
||||
|
||||
@@ -3261,12 +3261,13 @@ fn drain_pellet_fx(
|
||||
tracers: Option<Res<PreparedTracers>>,
|
||||
mut tracer_world: ResMut<TracerWorld>,
|
||||
mut gate: ResMut<TracerDrawGate>,
|
||||
(local, generation, timeline, mut occurrences, verdicts): (
|
||||
(local, generation, timeline, mut occurrences, verdicts, mut dyn_ent_hits): (
|
||||
Res<LocalPresentClient>,
|
||||
Res<frame::WorldGeneration>,
|
||||
Res<net::EntityEventCursor>,
|
||||
ResMut<PresentedFireFx>,
|
||||
Res<net::FireVerdictState>,
|
||||
MessageWriter<crate::adapters::anim::dyn_ent_wake::DynEntPelletHit>,
|
||||
),
|
||||
mut host: ResMut<HostFxSystem>,
|
||||
mut cursor: ResMut<FxJournalCursor>,
|
||||
@@ -3280,6 +3281,14 @@ fn drain_pellet_fx(
|
||||
if world != generation.stamp() {
|
||||
continue;
|
||||
}
|
||||
if domain != net::EntityEventDomain::Predicted && record.normal != [0.0; 3] {
|
||||
dyn_ent_hits.write(crate::adapters::anim::dyn_ent_wake::DynEntPelletHit {
|
||||
world,
|
||||
weapon: record.weapon,
|
||||
start: Vec3::from_array(record.start),
|
||||
end: Vec3::from_array(record.end),
|
||||
});
|
||||
}
|
||||
let now = host.0.msec_now;
|
||||
let request = FireFxRequest {
|
||||
world,
|
||||
|
||||
Reference in New Issue
Block a user