Keep muzzle flashes and attached effects playing past the first minutes

spawn_effect takes one of 255 bolt records for every effect played on a
tag, and nothing gave them back: run_garbage_collection cleared the
effect slot and dropped the record. A minute or two into a match with
bots, the muzzle flashes and shell ejects of every shot had used them
all, and from then on every bolted effect failed with EffectLimit and was
dropped without a word. That took later muzzle flashes, the jets'
afterburners and the precision airstrike's cluster bomb, which is played
on its tag_origin model and spawns the bomblets' ground explosions, so
the airstrike did its damage with no fire or smoke.

Garbage collection now puts a dead effect's record back on the free
list, and the ring-full check runs before a record is taken, so that
refusal no longer leaks one either.

Seen on an iPhone build on mp_terminal before the fix: Uzi muzzle flashes
at 0:30, none under steady fire at 2:40, and no fire or smoke where a
precision airstrike landed about 3 minutes in. With the fix, in another
match on the same map, the stealth bomber's fire and smoke showed and the
player's SCAR still flashed 4 minutes in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
JohnM
2026-10-13 20:27:30 +10:00
parent f46d5aadcd
commit 7d7bf24cd0
+15 -3
View File
@@ -860,6 +860,14 @@ impl FxSystemHost {
Some(idx as u8)
}
fn free_bolt_record(&mut self, index: u8) {
let Some(next) = self.bolt_next.get_mut(usize::from(index)) else {
return;
};
*next = self.bolt_first_free;
self.bolt_first_free = i32::from(index);
}
pub fn spawn_effect(
&mut self,
def_name: &str,
@@ -873,6 +881,9 @@ impl FxSystemHost {
if wants_spotlight && self.spotlight_count >= FX_SPOT_LIGHT_LIMIT {
return Err(SpawnFail::TooManySpotlights);
}
if self.ring_occupancy() >= FX_EFFECT_HANDLE_RING_SIZE as i32 {
return Err(SpawnFail::RingFull);
}
let bolt_index = if bolt == FX_SPAWN_BOLT_NONE {
0xff
} else {
@@ -883,9 +894,6 @@ impl FxSystemHost {
};
let alloc = self.first_free_effect;
if self.ring_occupancy() >= FX_EFFECT_HANDLE_RING_SIZE as i32 {
return Err(SpawnFail::RingFull);
}
self.first_free_effect = alloc.wrapping_add(1);
let ring_i = (alloc as u32 & (FX_EFFECT_HANDLE_RING_SIZE - 1)) as usize;
@@ -986,6 +994,10 @@ impl FxSystemHost {
self.set_handle_at_ring(active_index as u32, handle);
active_index = active_index.wrapping_add(1);
if let Some(slot) = handle_to_slot(handle) {
let bolt = self.effects[slot].bolt;
if bolt != 0xff {
self.free_bolt_record(bolt);
}
self.effects[slot] = FxEffectSlot::default();
}
}