From dd8ba62499722bc3c0b36330adcacfb80b498b6e Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 20 Aug 2026 09:22:29 +0200 Subject: [PATCH] chore: make explosion make match more vanilla --- crates/pumpkin/src/block/blocks/bed.rs | 6 +- .../src/block/blocks/respawn_anchor.rs | 6 +- .../src/entity/decoration/end_crystal.rs | 6 +- crates/pumpkin/src/entity/mob/creeper.rs | 8 +- .../pumpkin/src/entity/projectile/fireball.rs | 8 +- .../src/entity/projectile/wind_charge.rs | 36 +- crates/pumpkin/src/entity/tnt.rs | 15 +- .../src/entity/vehicle/minecart/tnt.rs | 4 +- .../loader/wasm/wasm_host/wit/v0_1/world.rs | 25 +- crates/pumpkin/src/world/explosion.rs | 380 ++++++++++++++---- crates/pumpkin/src/world/mod.rs | 63 ++- 11 files changed, 462 insertions(+), 95 deletions(-) diff --git a/crates/pumpkin/src/block/blocks/bed.rs b/crates/pumpkin/src/block/blocks/bed.rs index 808dce82a..3a9c9b4a1 100644 --- a/crates/pumpkin/src/block/blocks/bed.rs +++ b/crates/pumpkin/src/block/blocks/bed.rs @@ -244,7 +244,11 @@ impl BlockBehaviour for BedBlock { .await; args.world - .explode(bed_head_pos.to_centered_f64(), 5.0) + .explode( + bed_head_pos.to_centered_f64(), + 5.0, + crate::world::ExplosionInteraction::Block, + ) .await; return BlockActionResult::SuccessServer; diff --git a/crates/pumpkin/src/block/blocks/respawn_anchor.rs b/crates/pumpkin/src/block/blocks/respawn_anchor.rs index f35abadab..caf23833d 100644 --- a/crates/pumpkin/src/block/blocks/respawn_anchor.rs +++ b/crates/pumpkin/src/block/blocks/respawn_anchor.rs @@ -62,7 +62,11 @@ impl BlockBehaviour for RespawnAnchorBlock { .break_block(args.position, None, BlockFlags::SKIP_DROPS) .await; args.world - .explode(args.position.to_centered_f64(), 5.0) + .explode( + args.position.to_centered_f64(), + 5.0, + crate::world::ExplosionInteraction::Block, + ) .await; return BlockActionResult::SuccessServer; } diff --git a/crates/pumpkin/src/entity/decoration/end_crystal.rs b/crates/pumpkin/src/entity/decoration/end_crystal.rs index fa7173da8..509dafe57 100644 --- a/crates/pumpkin/src/entity/decoration/end_crystal.rs +++ b/crates/pumpkin/src/entity/decoration/end_crystal.rs @@ -56,7 +56,11 @@ impl EntityBase for EndCrystalEntity { self.entity .world .load() - .explode(self.entity.pos.load(), 6.0) + .explode( + self.entity.pos.load(), + 6.0, + crate::world::ExplosionInteraction::Block, + ) .await; } diff --git a/crates/pumpkin/src/entity/mob/creeper.rs b/crates/pumpkin/src/entity/mob/creeper.rs index 8d67cc0a2..78d2d78cc 100644 --- a/crates/pumpkin/src/entity/mob/creeper.rs +++ b/crates/pumpkin/src/entity/mob/creeper.rs @@ -115,7 +115,13 @@ impl CreeperEntity { .store(true, Ordering::Relaxed); let world = entity.world.load(); let pos = entity.pos.load(); - world.explode(pos, radius * multiplier).await; + world + .explode( + pos, + radius * multiplier, + crate::world::ExplosionInteraction::Mob, + ) + .await; // TODO: spawn area effect cloud with potion effects entity.remove().await; } diff --git a/crates/pumpkin/src/entity/projectile/fireball.rs b/crates/pumpkin/src/entity/projectile/fireball.rs index 26e8ca050..8362d56d6 100644 --- a/crates/pumpkin/src/entity/projectile/fireball.rs +++ b/crates/pumpkin/src/entity/projectile/fireball.rs @@ -272,7 +272,13 @@ impl EntityBase for FireballEntity { } let hit_pos = hit.hit_pos(); - world.explode(hit_pos, self.get_explosion_power()).await; + world + .explode( + hit_pos, + self.get_explosion_power(), + crate::world::ExplosionInteraction::Mob, + ) + .await; }) } } diff --git a/crates/pumpkin/src/entity/projectile/wind_charge.rs b/crates/pumpkin/src/entity/projectile/wind_charge.rs index b2d0f82d7..3e046aeb7 100644 --- a/crates/pumpkin/src/entity/projectile/wind_charge.rs +++ b/crates/pumpkin/src/entity/projectile/wind_charge.rs @@ -33,6 +33,31 @@ pub struct WindChargeEntity { thrown_item_entity: ThrownItemEntity, } +use crate::world::SimpleExplosionDamageCalculator; +use pumpkin_data::tag; +use std::sync::LazyLock; + +pub static WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR: LazyLock> = + LazyLock::new(|| { + Arc::new(SimpleExplosionDamageCalculator::new( + true, + false, + Some(1.22), + Some(&tag::Block::MINECRAFT_BLOCKS_WIND_CHARGE_EXPLOSIONS), + )) + }); + +pub static BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR: LazyLock< + Arc, +> = LazyLock::new(|| { + Arc::new(SimpleExplosionDamageCalculator::new( + true, + false, + None, + Some(&tag::Block::MINECRAFT_BLOCKS_WIND_CHARGE_EXPLOSIONS), + )) +}); + impl WindChargeEntity { #[must_use] pub const fn new_normal(thrown_item_entity: ThrownItemEntity) -> Self { @@ -64,10 +89,19 @@ impl WindChargeEntity { } pub async fn create_explosion(&self, position: Vector3) { + let calculator = match self.kind { + WindChargeKind::Normal { .. } => WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR.clone(), + WindChargeKind::Breeze => BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR.clone(), + }; self.get_entity() .world .load() - .explode(position, EXPLOSION_POWER) + .explode_with_calculator( + position, + EXPLOSION_POWER, + crate::world::ExplosionInteraction::Trigger, + Some(calculator), + ) .await; } diff --git a/crates/pumpkin/src/entity/tnt.rs b/crates/pumpkin/src/entity/tnt.rs index 8a18fd6c0..4d0d37a50 100644 --- a/crates/pumpkin/src/entity/tnt.rs +++ b/crates/pumpkin/src/entity/tnt.rs @@ -69,11 +69,16 @@ impl EntityBase for TNTEntity { if fuse <= 1 { // TNT explodes now self.entity.remove().await; - self.entity - .world - .load() - .explode(self.entity.pos.load(), self.power) - .await; + let world = self.entity.world.load(); + if world.level_info.load().game_rules.tnt_explodes { + world + .explode( + self.entity.pos.load(), + self.power, + crate::world::ExplosionInteraction::Tnt, + ) + .await; + } } else { // Safe decrement self.fuse.store(fuse - 1, Relaxed); diff --git a/crates/pumpkin/src/entity/vehicle/minecart/tnt.rs b/crates/pumpkin/src/entity/vehicle/minecart/tnt.rs index 2d8150bc2..d231c4111 100644 --- a/crates/pumpkin/src/entity/vehicle/minecart/tnt.rs +++ b/crates/pumpkin/src/entity/vehicle/minecart/tnt.rs @@ -99,7 +99,9 @@ impl TntMinecart { if primed { world.explode_tnt_minecart(pos, power).await; } else { - world.explode(pos, power).await; + world + .explode(pos, power, crate::world::ExplosionInteraction::Tnt) + .await; } } diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs index 466b84173..dc4e68e12 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs @@ -76,7 +76,7 @@ use crate::plugin::loader::wasm::wasm_host::{ }, wit::v0_1::pumpkin::{self, plugin::world::World}, }; -use crate::world::explosion::Explosion; +use crate::world::explosion::ExplosionInteraction; use pumpkin_data::game_rules::{GameRule, GameRuleValue}; pub(crate) fn from_wit_game_rule(rule: WitGameRule) -> GameRule { @@ -682,15 +682,24 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { pos: pumpkin::plugin::common::Position, power: f32, _create_fire: bool, - _interaction: pumpkin::plugin::world::ExplosionInteraction, + interaction: pumpkin::plugin::world::ExplosionInteraction, ) -> wasmtime::Result<()> { let world_ref = self.get_world_res(&world)?; - // Currently Explosion only supports power and position in this codebase - let explosion = Explosion::new( - power, - pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2), - ); - explosion.explode(&world_ref.provider).await; + let interaction = match interaction { + pumpkin::plugin::world::ExplosionInteraction::None => ExplosionInteraction::None, + pumpkin::plugin::world::ExplosionInteraction::Block => ExplosionInteraction::Block, + pumpkin::plugin::world::ExplosionInteraction::Mob => ExplosionInteraction::Mob, + pumpkin::plugin::world::ExplosionInteraction::Tnt => ExplosionInteraction::Tnt, + pumpkin::plugin::world::ExplosionInteraction::Trigger => ExplosionInteraction::Trigger, + }; + world_ref + .provider + .explode( + pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2), + power, + interaction, + ) + .await; Ok(()) } diff --git a/crates/pumpkin/src/world/explosion.rs b/crates/pumpkin/src/world/explosion.rs index 1d466626f..2eb24dad0 100644 --- a/crates/pumpkin/src/world/explosion.rs +++ b/crates/pumpkin/src/world/explosion.rs @@ -1,7 +1,11 @@ use std::sync::Arc; use pumpkin_data::{ - Block, BlockState, BlockStateId, damage::DamageType, entity::EntityType, fluid::Fluid, + Block, BlockState, BlockStateId, + damage::DamageType, + entity::EntityType, + fluid::Fluid, + tag::{Tag, Taggable}, }; use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3}; use pumpkin_world::chunk::ChunkData; @@ -15,22 +19,200 @@ use crate::{ use super::{BlockFlags, World}; +/// Defines the type of explosion interaction with the world. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ExplosionInteraction { + None, + Block, + Mob, + Tnt, + Trigger, +} + +/// Defines how an explosion interacts with blocks in the world. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BlockInteraction { + /// Keeps blocks intact (no block damage, no drops). + Keep, + /// Destroys blocks and drops 100% of items without decay. + Destroy, + /// Destroys blocks and applies loot decay based on explosion radius. + DestroyWithDecay, + /// Triggers block effects without destroying them. + TriggerBlock, +} + +/// Defines how damage and block destruction are calculated for an explosion. +pub trait ExplosionDamageCalculator: Send + Sync { + /// Returns the block's explosion resistance. If None, the block is treated as air/empty. + fn get_block_explosion_resistance( + &self, + _explosion: &Explosion, + _world: &World, + _pos: &BlockPos, + block: &Block, + fluid: &pumpkin_data::fluid::FluidState, + ) -> Option { + if block.default_state.is_air() && fluid.is_empty { + None + } else { + Some(fluid.blast_resistance.max(block.blast_resistance)) + } + } + + /// Returns whether this block should be destroyed / affected by the explosion. + fn should_block_explode( + &self, + _explosion: &Explosion, + _world: &World, + _pos: &BlockPos, + _block: &Block, + _power: f32, + ) -> bool { + true + } + + /// Returns whether the entity should take damage from the explosion. + fn should_damage_entity(&self, _explosion: &Explosion, _entity: &dyn EntityBase) -> bool { + true + } + + /// Returns knockback multiplier for the given entity (default 1.0). + fn get_knockback_multiplier(&self, _entity: &dyn EntityBase) -> f32 { + 1.0 + } + + /// Calculates the damage amount to deal to the entity given the exposure. + fn get_entity_damage_amount( + &self, + explosion: &Explosion, + entity: &dyn EntityBase, + exposure: f32, + ) -> f32 { + let radius = explosion.power as f64 * 2.0; + let distance = (entity + .get_entity() + .pos + .load() + .squared_distance_to_vec(&explosion.pos)) + .sqrt() + / radius; + let damage_multiplier = (1.0 - distance) * exposure as f64; + (f64::midpoint(damage_multiplier * damage_multiplier, damage_multiplier) + * 7.0 + * explosion.power as f64 + + 1.0) as f32 + } +} + +/// Default explosion damage calculator implementing vanilla standard explosion rules. +pub struct DefaultExplosionDamageCalculator; + +impl ExplosionDamageCalculator for DefaultExplosionDamageCalculator {} + +/// A configurable explosion damage calculator (e.g. for wind charges, mace wind bursts). +pub struct SimpleExplosionDamageCalculator { + pub damages_entities: bool, + pub damages_blocks: bool, + pub knockback_multiplier: Option, + pub immune_blocks: Option<&'static Tag>, +} + +impl SimpleExplosionDamageCalculator { + #[must_use] + pub const fn new( + damages_entities: bool, + damages_blocks: bool, + knockback_multiplier: Option, + immune_blocks: Option<&'static Tag>, + ) -> Self { + Self { + damages_entities, + damages_blocks, + knockback_multiplier, + immune_blocks, + } + } +} + +impl ExplosionDamageCalculator for SimpleExplosionDamageCalculator { + fn get_block_explosion_resistance( + &self, + _explosion: &Explosion, + _world: &World, + _pos: &BlockPos, + block: &Block, + fluid: &pumpkin_data::fluid::FluidState, + ) -> Option { + if let Some(immune_tag) = self.immune_blocks + && block.has_tag(immune_tag) + { + return None; + } + if block.default_state.is_air() && fluid.is_empty { + None + } else { + Some(fluid.blast_resistance.max(block.blast_resistance)) + } + } + + fn should_block_explode( + &self, + _explosion: &Explosion, + _world: &World, + _pos: &BlockPos, + block: &Block, + _power: f32, + ) -> bool { + if !self.damages_blocks { + return false; + } + if let Some(immune_tag) = self.immune_blocks + && block.has_tag(immune_tag) + { + return false; + } + true + } + + fn should_damage_entity(&self, _explosion: &Explosion, _entity: &dyn EntityBase) -> bool { + self.damages_entities + } + + fn get_knockback_multiplier(&self, _entity: &dyn EntityBase) -> f32 { + self.knockback_multiplier.unwrap_or(1.0) + } +} + pub struct Explosion { power: f32, pos: Vector3, + block_interaction: BlockInteraction, + damage_calculator: Option>, preserve_rails: bool, } impl Explosion { #[must_use] - pub const fn new(power: f32, pos: Vector3) -> Self { + pub const fn new(power: f32, pos: Vector3, block_interaction: BlockInteraction) -> Self { Self { power, pos, + block_interaction, + damage_calculator: None, preserve_rails: false, } } + #[must_use] + pub fn with_damage_calculator( + mut self, + calculator: Arc, + ) -> Self { + self.damage_calculator = Some(calculator); + self + } + #[must_use] pub const fn preserving_rails(mut self) -> Self { self.preserve_rails = true; @@ -48,6 +230,7 @@ impl Explosion { || block.id == Block::ACTIVATOR_RAIL.id } + #[allow(clippy::too_many_lines)] fn get_blocks_to_destroy( &self, world: &World, @@ -59,6 +242,12 @@ impl Explosion { Option>, > = FxHashMap::default(); + let default_calc = DefaultExplosionDamageCalculator; + let calc: &dyn ExplosionDamageCalculator = match &self.damage_calculator { + Some(c) => c.as_ref(), + None => &default_calc, + }; + for x in 0..16 { for y in 0..16 { for z in 0..16 { @@ -137,14 +326,25 @@ impl Explosion { if !state.is_air() || !fluid_state.is_empty { let protects_rail = self.protects_rail(world, &block_pos, block); let resistance = if protects_rail { - 0.0 + Some(0.0) } else { - fluid_state.blast_resistance.max(block.blast_resistance) + calc.get_block_explosion_resistance( + self, + world, + &block_pos, + block, + fluid_state, + ) }; - h -= (resistance + 0.3) * 0.3; + if let Some(resistance) = resistance { + h -= (resistance + 0.3) * 0.3; + } - if h > 0.0 && !protects_rail { + if h > 0.0 + && !protects_rail + && calc.should_block_explode(self, world, &block_pos, block, h) + { map.insert(block_pos, (block, state)); } } @@ -181,6 +381,12 @@ impl Explosion { let entities = world.get_all_at_box(&search_box); + let default_calc = DefaultExplosionDamageCalculator; + let calc: &dyn ExplosionDamageCalculator = match &self.damage_calculator { + Some(c) => c.as_ref(), + None => &default_calc, + }; + for entity_base in entities { if entity_base.is_immune_to_explosion() { continue; @@ -198,21 +404,26 @@ impl Explosion { continue; } - let exposure = Self::calculate_exposure(&self.pos, entity, world).await as f64; + let should_damage = calc.should_damage_entity(self, entity_base.as_ref()); + let knockback_multiplier = calc.get_knockback_multiplier(entity_base.as_ref()) as f64; + + let exposure = if !should_damage && knockback_multiplier == 0.0 { + 0.0 + } else { + Self::calculate_exposure(&self.pos, entity, world).await as f64 + }; + if exposure == 0.0 { continue; } - let damage_multiplier = (1.0 - distance) * exposure; - let damage = (f64::midpoint(damage_multiplier * damage_multiplier, damage_multiplier) - * 7.0 - * self.power as f64 - + 1.0) as f32; - - // TODO: damage type - entity - .damage(entity_base.as_ref(), damage, DamageType::EXPLOSION) - .await; + if should_damage { + let damage = + calc.get_entity_damage_amount(self, entity_base.as_ref(), exposure as f32); + entity + .damage(entity_base.as_ref(), damage, DamageType::EXPLOSION) + .await; + } // Calculate and apply knockback let dir_pos = if entity.entity_type == &EntityType::TNT { @@ -221,11 +432,12 @@ impl Explosion { entity.get_eye_pos() }; let direction = (dir_pos - self.pos).normalize(); - // TODO + // TODO: entity explosion knockback resistance attribute let knockback_resistance = 0.0; - let knockback_multiplier = (1.0 - distance) * exposure * (1.0 - knockback_resistance); - let knockback = direction * knockback_multiplier; + let knockback_power = + (1.0 - distance) * exposure * knockback_multiplier * (1.0 - knockback_resistance); + let knockback = direction * knockback_power; entity.add_velocity(knockback); } } @@ -291,62 +503,88 @@ impl Explosion { /// Returns the removed block count pub async fn explode(&self, world: &Arc) -> u32 { - let center_pos = BlockPos::floored(self.pos.x, self.pos.y, self.pos.z); - let mut event = crate::plugin::api::events::block::block_explode::BlockExplodeEvent::new( - center_pos, - if self.power > 0.0 { - 1.0 / self.power - } else { - 1.0 - }, - ); - if let Some(server) = world.server.upgrade() { - server.plugin_manager.fire(&server, &mut event).await; - } - if event.cancelled { - return 0; - } - - let blocks = self.get_blocks_to_destroy(world); self.damage_entities(world).await; - for (pos, (block, state)) in &blocks { - world - .set_block_state(pos, BlockStateId::AIR, BlockFlags::NOTIFY_ALL) - .await; - world.close_container_screens_at(pos).await; - let pumpkin_block = world.block_registry.get_pumpkin_block(block.id); - - if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) { - let is_raining = world.is_raining().await; - let is_thundering = world.is_thundering().await; - let params = LootContextParameters { - block_state: Some(state), - explosion_radius: Some(self.power), - position: Some(pumpkin_util::math::vector3::Vector3::new( - pos.0.x as f64, - pos.0.y as f64, - pos.0.z as f64, - )), - world_time: world.level_info.load().day_time as u64, - is_raining: Some(is_raining), - is_thundering: Some(is_thundering), - ..Default::default() - }; - drop_loot(world, block, pos, false, params).await; + match self.block_interaction { + BlockInteraction::Keep => 0, + BlockInteraction::TriggerBlock => { + let blocks = self.get_blocks_to_destroy(world); + for (pos, (block, _state)) in &blocks { + let pumpkin_block = world.block_registry.get_pumpkin_block(block.id); + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .explode(ExplodeArgs { + world, + block, + position: pos, + }) + .await; + } + } + 0 } - if let Some(pumpkin_block) = pumpkin_block { - pumpkin_block - .explode(ExplodeArgs { - world, - block, - position: pos, - }) - .await; + BlockInteraction::Destroy | BlockInteraction::DestroyWithDecay => { + let center_pos = BlockPos::floored(self.pos.x, self.pos.y, self.pos.z); + let mut event = + crate::plugin::api::events::block::block_explode::BlockExplodeEvent::new( + center_pos, + if self.power > 0.0 { + 1.0 / self.power + } else { + 1.0 + }, + ); + if let Some(server) = world.server.upgrade() { + server.plugin_manager.fire(&server, &mut event).await; + } + if event.cancelled { + return 0; + } + + let blocks = self.get_blocks_to_destroy(world); + let decay_drops = self.block_interaction == BlockInteraction::DestroyWithDecay; + let explosion_radius = decay_drops.then_some(self.power); + + for (pos, (block, state)) in &blocks { + world + .set_block_state(pos, BlockStateId::AIR, BlockFlags::NOTIFY_ALL) + .await; + world.close_container_screens_at(pos).await; + + let pumpkin_block = world.block_registry.get_pumpkin_block(block.id); + + if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) { + let is_raining = world.is_raining().await; + let is_thundering = world.is_thundering().await; + let params = LootContextParameters { + block_state: Some(state), + explosion_radius, + position: Some(pumpkin_util::math::vector3::Vector3::new( + pos.0.x as f64, + pos.0.y as f64, + pos.0.z as f64, + )), + world_time: world.level_info.load().day_time as u64, + is_raining: Some(is_raining), + is_thundering: Some(is_thundering), + ..Default::default() + }; + drop_loot(world, block, pos, false, params).await; + } + if let Some(pumpkin_block) = pumpkin_block { + pumpkin_block + .explode(ExplodeArgs { + world, + block, + position: pos, + }) + .await; + } + } + // TODO: fire + blocks.len() as u32 } } - // TODO: fire - blocks.len() as u32 } } diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index a333dbc8e..5cdcf9333 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -53,7 +53,10 @@ use crate::{ use arc_swap::ArcSwap; use border::Worldborder; use bytes::{BufMut, Bytes}; -use explosion::Explosion; +pub use explosion::{ + BlockInteraction, DefaultExplosionDamageCalculator, Explosion, ExplosionDamageCalculator, + ExplosionInteraction, SimpleExplosionDamageCalculator, +}; use pumpkin_config::BasicConfiguration; use pumpkin_data::block_properties::{blocks_movement, is_air}; use pumpkin_data::block_rotation::{Mirror, Rotation}; @@ -3797,16 +3800,68 @@ impl World { player.set_health(20.0).await; } - pub async fn explode(self: &Arc, position: Vector3, power: f32) { - let explosion = Explosion::new(power, position); + pub async fn explode( + self: &Arc, + position: Vector3, + power: f32, + interaction: ExplosionInteraction, + ) { + self.explode_with_calculator(position, power, interaction, None) + .await; + } + + pub async fn explode_with_calculator( + self: &Arc, + position: Vector3, + power: f32, + interaction: ExplosionInteraction, + damage_calculator: Option>, + ) { + let block_interaction = self.get_block_interaction(interaction); + let mut explosion = Explosion::new(power, position, block_interaction); + if let Some(calc) = damage_calculator { + explosion = explosion.with_damage_calculator(calc); + } self.run_explosion(explosion, position, power).await; } pub async fn explode_tnt_minecart(self: &Arc, position: Vector3, power: f32) { - let explosion = Explosion::new(power, position).preserving_rails(); + let block_interaction = self.get_block_interaction(ExplosionInteraction::Tnt); + let explosion = Explosion::new(power, position, block_interaction).preserving_rails(); self.run_explosion(explosion, position, power).await; } + #[must_use] + pub fn get_block_interaction(&self, interaction: ExplosionInteraction) -> BlockInteraction { + let game_rules = &self.level_info.load().game_rules; + match interaction { + ExplosionInteraction::None => BlockInteraction::Keep, + ExplosionInteraction::Block => { + Self::get_destroy_type(game_rules.block_explosion_drop_decay) + } + ExplosionInteraction::Mob => { + if game_rules.mob_griefing { + Self::get_destroy_type(game_rules.mob_explosion_drop_decay) + } else { + BlockInteraction::Keep + } + } + ExplosionInteraction::Tnt => { + Self::get_destroy_type(game_rules.tnt_explosion_drop_decay) + } + ExplosionInteraction::Trigger => BlockInteraction::TriggerBlock, + } + } + + #[must_use] + pub const fn get_destroy_type(drop_decay: bool) -> BlockInteraction { + if drop_decay { + BlockInteraction::DestroyWithDecay + } else { + BlockInteraction::Destroy + } + } + async fn run_explosion( self: &Arc, explosion: Explosion,