mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
chore: make explosion make match more vanilla
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Arc<SimpleExplosionDamageCalculator>> =
|
||||
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<SimpleExplosionDamageCalculator>,
|
||||
> = 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<f64>) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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<f32> {
|
||||
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<f32>,
|
||||
pub immune_blocks: Option<&'static Tag>,
|
||||
}
|
||||
|
||||
impl SimpleExplosionDamageCalculator {
|
||||
#[must_use]
|
||||
pub const fn new(
|
||||
damages_entities: bool,
|
||||
damages_blocks: bool,
|
||||
knockback_multiplier: Option<f32>,
|
||||
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<f32> {
|
||||
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<f64>,
|
||||
block_interaction: BlockInteraction,
|
||||
damage_calculator: Option<Arc<dyn ExplosionDamageCalculator>>,
|
||||
preserve_rails: bool,
|
||||
}
|
||||
|
||||
impl Explosion {
|
||||
#[must_use]
|
||||
pub const fn new(power: f32, pos: Vector3<f64>) -> Self {
|
||||
pub const fn new(power: f32, pos: Vector3<f64>, 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<dyn ExplosionDamageCalculator>,
|
||||
) -> 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<Arc<ChunkData>>,
|
||||
> = 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<World>) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Self>, position: Vector3<f64>, power: f32) {
|
||||
let explosion = Explosion::new(power, position);
|
||||
pub async fn explode(
|
||||
self: &Arc<Self>,
|
||||
position: Vector3<f64>,
|
||||
power: f32,
|
||||
interaction: ExplosionInteraction,
|
||||
) {
|
||||
self.explode_with_calculator(position, power, interaction, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn explode_with_calculator(
|
||||
self: &Arc<Self>,
|
||||
position: Vector3<f64>,
|
||||
power: f32,
|
||||
interaction: ExplosionInteraction,
|
||||
damage_calculator: Option<Arc<dyn ExplosionDamageCalculator>>,
|
||||
) {
|
||||
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<Self>, position: Vector3<f64>, 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<Self>,
|
||||
explosion: Explosion,
|
||||
|
||||
Reference in New Issue
Block a user