chore: make explosion make match more vanilla
This commit is contained in:
parent
0844e92911
commit
dd8ba62499
@ -244,7 +244,11 @@ impl BlockBehaviour for BedBlock {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
args.world
|
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;
|
.await;
|
||||||
|
|
||||||
return BlockActionResult::SuccessServer;
|
return BlockActionResult::SuccessServer;
|
||||||
|
|||||||
@ -62,7 +62,11 @@ impl BlockBehaviour for RespawnAnchorBlock {
|
|||||||
.break_block(args.position, None, BlockFlags::SKIP_DROPS)
|
.break_block(args.position, None, BlockFlags::SKIP_DROPS)
|
||||||
.await;
|
.await;
|
||||||
args.world
|
args.world
|
||||||
.explode(args.position.to_centered_f64(), 5.0)
|
.explode(
|
||||||
|
args.position.to_centered_f64(),
|
||||||
|
5.0,
|
||||||
|
crate::world::ExplosionInteraction::Block,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
return BlockActionResult::SuccessServer;
|
return BlockActionResult::SuccessServer;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,11 @@ impl EntityBase for EndCrystalEntity {
|
|||||||
self.entity
|
self.entity
|
||||||
.world
|
.world
|
||||||
.load()
|
.load()
|
||||||
.explode(self.entity.pos.load(), 6.0)
|
.explode(
|
||||||
|
self.entity.pos.load(),
|
||||||
|
6.0,
|
||||||
|
crate::world::ExplosionInteraction::Block,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,7 +115,13 @@ impl CreeperEntity {
|
|||||||
.store(true, Ordering::Relaxed);
|
.store(true, Ordering::Relaxed);
|
||||||
let world = entity.world.load();
|
let world = entity.world.load();
|
||||||
let pos = entity.pos.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
|
// TODO: spawn area effect cloud with potion effects
|
||||||
entity.remove().await;
|
entity.remove().await;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -272,7 +272,13 @@ impl EntityBase for FireballEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hit_pos = hit.hit_pos();
|
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,
|
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 {
|
impl WindChargeEntity {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new_normal(thrown_item_entity: ThrownItemEntity) -> Self {
|
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>) {
|
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()
|
self.get_entity()
|
||||||
.world
|
.world
|
||||||
.load()
|
.load()
|
||||||
.explode(position, EXPLOSION_POWER)
|
.explode_with_calculator(
|
||||||
|
position,
|
||||||
|
EXPLOSION_POWER,
|
||||||
|
crate::world::ExplosionInteraction::Trigger,
|
||||||
|
Some(calculator),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -69,11 +69,16 @@ impl EntityBase for TNTEntity {
|
|||||||
if fuse <= 1 {
|
if fuse <= 1 {
|
||||||
// TNT explodes now
|
// TNT explodes now
|
||||||
self.entity.remove().await;
|
self.entity.remove().await;
|
||||||
self.entity
|
let world = self.entity.world.load();
|
||||||
.world
|
if world.level_info.load().game_rules.tnt_explodes {
|
||||||
.load()
|
world
|
||||||
.explode(self.entity.pos.load(), self.power)
|
.explode(
|
||||||
.await;
|
self.entity.pos.load(),
|
||||||
|
self.power,
|
||||||
|
crate::world::ExplosionInteraction::Tnt,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Safe decrement
|
// Safe decrement
|
||||||
self.fuse.store(fuse - 1, Relaxed);
|
self.fuse.store(fuse - 1, Relaxed);
|
||||||
|
|||||||
@ -99,7 +99,9 @@ impl TntMinecart {
|
|||||||
if primed {
|
if primed {
|
||||||
world.explode_tnt_minecart(pos, power).await;
|
world.explode_tnt_minecart(pos, power).await;
|
||||||
} else {
|
} 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},
|
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};
|
use pumpkin_data::game_rules::{GameRule, GameRuleValue};
|
||||||
|
|
||||||
pub(crate) fn from_wit_game_rule(rule: WitGameRule) -> GameRule {
|
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,
|
pos: pumpkin::plugin::common::Position,
|
||||||
power: f32,
|
power: f32,
|
||||||
_create_fire: bool,
|
_create_fire: bool,
|
||||||
_interaction: pumpkin::plugin::world::ExplosionInteraction,
|
interaction: pumpkin::plugin::world::ExplosionInteraction,
|
||||||
) -> wasmtime::Result<()> {
|
) -> wasmtime::Result<()> {
|
||||||
let world_ref = self.get_world_res(&world)?;
|
let world_ref = self.get_world_res(&world)?;
|
||||||
// Currently Explosion only supports power and position in this codebase
|
let interaction = match interaction {
|
||||||
let explosion = Explosion::new(
|
pumpkin::plugin::world::ExplosionInteraction::None => ExplosionInteraction::None,
|
||||||
power,
|
pumpkin::plugin::world::ExplosionInteraction::Block => ExplosionInteraction::Block,
|
||||||
pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2),
|
pumpkin::plugin::world::ExplosionInteraction::Mob => ExplosionInteraction::Mob,
|
||||||
);
|
pumpkin::plugin::world::ExplosionInteraction::Tnt => ExplosionInteraction::Tnt,
|
||||||
explosion.explode(&world_ref.provider).await;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use pumpkin_data::{
|
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_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
||||||
use pumpkin_world::chunk::ChunkData;
|
use pumpkin_world::chunk::ChunkData;
|
||||||
@ -15,22 +19,200 @@ use crate::{
|
|||||||
|
|
||||||
use super::{BlockFlags, World};
|
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 {
|
pub struct Explosion {
|
||||||
power: f32,
|
power: f32,
|
||||||
pos: Vector3<f64>,
|
pos: Vector3<f64>,
|
||||||
|
block_interaction: BlockInteraction,
|
||||||
|
damage_calculator: Option<Arc<dyn ExplosionDamageCalculator>>,
|
||||||
preserve_rails: bool,
|
preserve_rails: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Explosion {
|
impl Explosion {
|
||||||
#[must_use]
|
#[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 {
|
Self {
|
||||||
power,
|
power,
|
||||||
pos,
|
pos,
|
||||||
|
block_interaction,
|
||||||
|
damage_calculator: None,
|
||||||
preserve_rails: false,
|
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]
|
#[must_use]
|
||||||
pub const fn preserving_rails(mut self) -> Self {
|
pub const fn preserving_rails(mut self) -> Self {
|
||||||
self.preserve_rails = true;
|
self.preserve_rails = true;
|
||||||
@ -48,6 +230,7 @@ impl Explosion {
|
|||||||
|| block.id == Block::ACTIVATOR_RAIL.id
|
|| block.id == Block::ACTIVATOR_RAIL.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn get_blocks_to_destroy(
|
fn get_blocks_to_destroy(
|
||||||
&self,
|
&self,
|
||||||
world: &World,
|
world: &World,
|
||||||
@ -59,6 +242,12 @@ impl Explosion {
|
|||||||
Option<Arc<ChunkData>>,
|
Option<Arc<ChunkData>>,
|
||||||
> = FxHashMap::default();
|
> = 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 x in 0..16 {
|
||||||
for y in 0..16 {
|
for y in 0..16 {
|
||||||
for z in 0..16 {
|
for z in 0..16 {
|
||||||
@ -137,14 +326,25 @@ impl Explosion {
|
|||||||
if !state.is_air() || !fluid_state.is_empty {
|
if !state.is_air() || !fluid_state.is_empty {
|
||||||
let protects_rail = self.protects_rail(world, &block_pos, block);
|
let protects_rail = self.protects_rail(world, &block_pos, block);
|
||||||
let resistance = if protects_rail {
|
let resistance = if protects_rail {
|
||||||
0.0
|
Some(0.0)
|
||||||
} else {
|
} 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));
|
map.insert(block_pos, (block, state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,6 +381,12 @@ impl Explosion {
|
|||||||
|
|
||||||
let entities = world.get_all_at_box(&search_box);
|
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 {
|
for entity_base in entities {
|
||||||
if entity_base.is_immune_to_explosion() {
|
if entity_base.is_immune_to_explosion() {
|
||||||
continue;
|
continue;
|
||||||
@ -198,21 +404,26 @@ impl Explosion {
|
|||||||
continue;
|
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 {
|
if exposure == 0.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let damage_multiplier = (1.0 - distance) * exposure;
|
if should_damage {
|
||||||
let damage = (f64::midpoint(damage_multiplier * damage_multiplier, damage_multiplier)
|
let damage =
|
||||||
* 7.0
|
calc.get_entity_damage_amount(self, entity_base.as_ref(), exposure as f32);
|
||||||
* self.power as f64
|
entity
|
||||||
+ 1.0) as f32;
|
.damage(entity_base.as_ref(), damage, DamageType::EXPLOSION)
|
||||||
|
.await;
|
||||||
// TODO: damage type
|
}
|
||||||
entity
|
|
||||||
.damage(entity_base.as_ref(), damage, DamageType::EXPLOSION)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Calculate and apply knockback
|
// Calculate and apply knockback
|
||||||
let dir_pos = if entity.entity_type == &EntityType::TNT {
|
let dir_pos = if entity.entity_type == &EntityType::TNT {
|
||||||
@ -221,11 +432,12 @@ impl Explosion {
|
|||||||
entity.get_eye_pos()
|
entity.get_eye_pos()
|
||||||
};
|
};
|
||||||
let direction = (dir_pos - self.pos).normalize();
|
let direction = (dir_pos - self.pos).normalize();
|
||||||
// TODO
|
// TODO: entity explosion knockback resistance attribute
|
||||||
let knockback_resistance = 0.0;
|
let knockback_resistance = 0.0;
|
||||||
|
|
||||||
let knockback_multiplier = (1.0 - distance) * exposure * (1.0 - knockback_resistance);
|
let knockback_power =
|
||||||
let knockback = direction * knockback_multiplier;
|
(1.0 - distance) * exposure * knockback_multiplier * (1.0 - knockback_resistance);
|
||||||
|
let knockback = direction * knockback_power;
|
||||||
entity.add_velocity(knockback);
|
entity.add_velocity(knockback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,62 +503,88 @@ impl Explosion {
|
|||||||
|
|
||||||
/// Returns the removed block count
|
/// Returns the removed block count
|
||||||
pub async fn explode(&self, world: &Arc<World>) -> u32 {
|
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;
|
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);
|
match self.block_interaction {
|
||||||
|
BlockInteraction::Keep => 0,
|
||||||
if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) {
|
BlockInteraction::TriggerBlock => {
|
||||||
let is_raining = world.is_raining().await;
|
let blocks = self.get_blocks_to_destroy(world);
|
||||||
let is_thundering = world.is_thundering().await;
|
for (pos, (block, _state)) in &blocks {
|
||||||
let params = LootContextParameters {
|
let pumpkin_block = world.block_registry.get_pumpkin_block(block.id);
|
||||||
block_state: Some(state),
|
if let Some(pumpkin_block) = pumpkin_block {
|
||||||
explosion_radius: Some(self.power),
|
pumpkin_block
|
||||||
position: Some(pumpkin_util::math::vector3::Vector3::new(
|
.explode(ExplodeArgs {
|
||||||
pos.0.x as f64,
|
world,
|
||||||
pos.0.y as f64,
|
block,
|
||||||
pos.0.z as f64,
|
position: pos,
|
||||||
)),
|
})
|
||||||
world_time: world.level_info.load().day_time as u64,
|
.await;
|
||||||
is_raining: Some(is_raining),
|
}
|
||||||
is_thundering: Some(is_thundering),
|
}
|
||||||
..Default::default()
|
0
|
||||||
};
|
|
||||||
drop_loot(world, block, pos, false, params).await;
|
|
||||||
}
|
}
|
||||||
if let Some(pumpkin_block) = pumpkin_block {
|
BlockInteraction::Destroy | BlockInteraction::DestroyWithDecay => {
|
||||||
pumpkin_block
|
let center_pos = BlockPos::floored(self.pos.x, self.pos.y, self.pos.z);
|
||||||
.explode(ExplodeArgs {
|
let mut event =
|
||||||
world,
|
crate::plugin::api::events::block::block_explode::BlockExplodeEvent::new(
|
||||||
block,
|
center_pos,
|
||||||
position: pos,
|
if self.power > 0.0 {
|
||||||
})
|
1.0 / self.power
|
||||||
.await;
|
} 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 arc_swap::ArcSwap;
|
||||||
use border::Worldborder;
|
use border::Worldborder;
|
||||||
use bytes::{BufMut, Bytes};
|
use bytes::{BufMut, Bytes};
|
||||||
use explosion::Explosion;
|
pub use explosion::{
|
||||||
|
BlockInteraction, DefaultExplosionDamageCalculator, Explosion, ExplosionDamageCalculator,
|
||||||
|
ExplosionInteraction, SimpleExplosionDamageCalculator,
|
||||||
|
};
|
||||||
use pumpkin_config::BasicConfiguration;
|
use pumpkin_config::BasicConfiguration;
|
||||||
use pumpkin_data::block_properties::{blocks_movement, is_air};
|
use pumpkin_data::block_properties::{blocks_movement, is_air};
|
||||||
use pumpkin_data::block_rotation::{Mirror, Rotation};
|
use pumpkin_data::block_rotation::{Mirror, Rotation};
|
||||||
@ -3797,16 +3800,68 @@ impl World {
|
|||||||
player.set_health(20.0).await;
|
player.set_health(20.0).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn explode(self: &Arc<Self>, position: Vector3<f64>, power: f32) {
|
pub async fn explode(
|
||||||
let explosion = Explosion::new(power, position);
|
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;
|
self.run_explosion(explosion, position, power).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn explode_tnt_minecart(self: &Arc<Self>, position: Vector3<f64>, power: f32) {
|
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;
|
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(
|
async fn run_explosion(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
explosion: Explosion,
|
explosion: Explosion,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user