From 0844e929112d5cda772bc8b0de51e38930142704 Mon Sep 17 00:00:00 2001 From: Maksas Gornostajus <82242062+Max-109@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:49:13 +0300 Subject: [PATCH] fix(entity): let explosions push TNT (#2941) `TNTEntity` claimed to be immune to explosions, and `damage_entities` drops immune entities before it gets anywhere near the knockback, so a chained TNT never got the outward kick. Vanilla's TNT entity does not override `ignoreExplosion`. The TNT case for the knockback origin in `damage_entities` was unreachable until now. `tick` also snapshotted velocity before moving and wrote back from that stale value, so a push landing during the awaits was thrown away. Entities tick concurrently, so this happened often enough to matter. It reads the velocity back after `move_entity` now, like vanilla. --- crates/pumpkin/src/entity/tnt.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/pumpkin/src/entity/tnt.rs b/crates/pumpkin/src/entity/tnt.rs index 481b95a87..8a18fd6c0 100644 --- a/crates/pumpkin/src/entity/tnt.rs +++ b/crates/pumpkin/src/entity/tnt.rs @@ -41,13 +41,17 @@ impl EntityBase for TNTEntity { ) -> EntityBaseFuture<'a, ()> { Box::pin(async move { let entity = &self.entity; - let original_velo = entity.velocity.load(); - let mut velo = original_velo; + let mut velo = entity.velocity.load(); velo.y -= self.get_gravity(); entity.move_entity(caller, velo).await; entity.tick_block_collisions(caller, server).await; + + // Read back what actually happened instead of reusing the pre-move + // value: `move_entity` clamps on collision, and an explosion may have + // pushed us while we were awaiting above + let velo = entity.velocity.load(); if entity.on_ground.load(Ordering::Relaxed) { entity.velocity.store(velo.multiply(0.7, -0.5, 0.7)); } else { @@ -120,8 +124,4 @@ impl EntityBase for TNTEntity { fn cast_any(&self) -> &dyn std::any::Any { self } - - fn is_immune_to_explosion(&self) -> bool { - true - } }