From 59b7e0a14ef44ff4ec63c62866a4e2483a1136b0 Mon Sep 17 00:00:00 2001 From: Musawer <118316797+TheRealMusawer@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:50:23 -0500 Subject: [PATCH] Fix spectator explosion immunity and TNT fuse underflow (#1587) (#1588) * made it so spectators don't take knockback from explosions Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * fix(tnt): prevent fuse underflow Underflow Prevention: By checking if current_fuse <= 1 before subtracting, we ensure the fuse never rolls over from 0 to 4,294,967,295. Instant Summon Fix: If a player uses /summon tnt and the fuse is somehow initialized to 0, it will now explode on the very first tick instead of bouncing forever. Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * fix Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * Update explosion.rs Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * fixes Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * fix spectator check Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * fix Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> * CLIPPY Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> --------- Signed-off-by: Musawer <118316797+TheRealMusawer@users.noreply.github.com> --- pumpkin/src/entity/tnt.rs | 17 ++++++++++------- pumpkin/src/world/explosion.rs | 8 ++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pumpkin/src/entity/tnt.rs b/pumpkin/src/entity/tnt.rs index 195f14fc5..492cd2823 100644 --- a/pumpkin/src/entity/tnt.rs +++ b/pumpkin/src/entity/tnt.rs @@ -49,19 +49,21 @@ impl EntityBase for TNTEntity { entity.move_entity(caller.clone(), velo).await; entity.tick_block_collisions(&caller, server).await; entity.velocity.store(velo.multiply(0.98, 0.98, 0.98)); + if entity.on_ground.load(Ordering::Relaxed) { entity.velocity.store(velo.multiply(0.7, -0.5, 0.7)); } - let velocity_dirty = entity.velocity_dirty.swap(false, Ordering::SeqCst); - if velocity_dirty { + if entity.velocity_dirty.swap(false, Ordering::SeqCst) { entity.send_pos_rot().await; - entity.send_velocity().await; } - let fuse = self.fuse.fetch_sub(1, Relaxed); - if fuse == 0 { + // FIX: Prevent fuse underflow (vanilla parity) + let fuse = self.fuse.load(Relaxed); + + if fuse <= 1 { + // TNT explodes now self.entity.remove().await; self.entity .world @@ -69,6 +71,8 @@ impl EntityBase for TNTEntity { .explode(self.entity.pos.load(), self.power) .await; } else { + // Safe decrement + self.fuse.store(fuse - 1, Relaxed); entity.update_fluid_state(&caller).await; } }) @@ -76,13 +80,12 @@ impl EntityBase for TNTEntity { fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> { Box::pin(async { - // TODO: Yes, this is the wrong function, but we need to send this after spawning the entity. let pos: f64 = rand::random::() * TAU; self.entity .set_velocity(Vector3::new(-pos.sin() * 0.02, 0.2, -pos.cos() * 0.02)) .await; - // We can merge multiple `Metadata`s into one meta packet. + self.entity .send_meta_data(&[ Metadata::new( diff --git a/pumpkin/src/world/explosion.rs b/pumpkin/src/world/explosion.rs index 731f7c27f..8abf2f4a1 100644 --- a/pumpkin/src/world/explosion.rs +++ b/pumpkin/src/world/explosion.rs @@ -16,11 +16,13 @@ pub struct Explosion { power: f32, pos: Vector3, } + impl Explosion { #[must_use] pub const fn new(power: f32, pos: Vector3) -> Self { Self { power, pos } } + async fn get_blocks_to_destroy( &self, world: &World, @@ -103,6 +105,12 @@ impl Explosion { if entity_base.is_immune_to_explosion() { continue; } + + // Skip spectators (no damage, no knockback) + if entity_base.is_spectator() { + continue; + } + let entity = entity_base.get_entity(); let distance = (entity.pos.load().squared_distance_to_vec(&self.pos)).sqrt() / radius;