From 8feb6c9b934779898a0d8315dcab2e586743a787 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 21 Aug 2025 17:54:07 +0200 Subject: [PATCH] feat: add end crystals --- pumpkin/src/block/blocks/bed.rs | 2 +- pumpkin/src/entity/decoration/end_crystal.rs | 67 +++++++++++++++++++ pumpkin/src/entity/decoration/mod.rs | 1 + pumpkin/src/entity/falling.rs | 4 +- pumpkin/src/entity/living.rs | 3 +- pumpkin/src/entity/tnt.rs | 4 +- pumpkin/src/entity/type.rs | 7 +- pumpkin/src/item/items/axe.rs | 4 +- pumpkin/src/item/items/end_crystal.rs | 58 ++++++++++++++++ pumpkin/src/item/items/ender_eye.rs | 3 +- pumpkin/src/item/items/hoe.rs | 2 +- pumpkin/src/item/items/honeycomb.rs | 3 +- pumpkin/src/item/items/ignite/fire_charge.rs | 4 +- .../src/item/items/ignite/flint_and_steel.rs | 4 +- pumpkin/src/item/items/ignite/ignition.rs | 2 - pumpkin/src/item/items/minecart.rs | 5 +- pumpkin/src/item/items/mod.rs | 3 + pumpkin/src/item/items/shovel.rs | 4 +- pumpkin/src/item/mod.rs | 3 +- pumpkin/src/item/registry.rs | 7 +- pumpkin/src/net/java/play.rs | 16 ++--- pumpkin/src/world/explosion.rs | 5 +- pumpkin/src/world/mod.rs | 4 +- 23 files changed, 173 insertions(+), 42 deletions(-) create mode 100644 pumpkin/src/entity/decoration/end_crystal.rs create mode 100644 pumpkin/src/item/items/end_crystal.rs diff --git a/pumpkin/src/block/blocks/bed.rs b/pumpkin/src/block/blocks/bed.rs index 02547a565..221ba0b25 100644 --- a/pumpkin/src/block/blocks/bed.rs +++ b/pumpkin/src/block/blocks/bed.rs @@ -164,7 +164,7 @@ impl BlockBehaviour for BedBlock { .await; args.world - .explode(args.server, bed_head_pos.to_centered_f64(), 5.0) + .explode(bed_head_pos.to_centered_f64(), 5.0) .await; return BlockActionResult::SuccessServer; diff --git a/pumpkin/src/entity/decoration/end_crystal.rs b/pumpkin/src/entity/decoration/end_crystal.rs new file mode 100644 index 000000000..e7183a431 --- /dev/null +++ b/pumpkin/src/entity/decoration/end_crystal.rs @@ -0,0 +1,67 @@ +use core::f32; +use std::sync::Arc; + +use crate::entity::{Entity, EntityBase, NBTStorage, living::LivingEntity}; +use async_trait::async_trait; +use pumpkin_data::damage::DamageType; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_protocol::java::client::play::{MetaDataType, Metadata}; +use pumpkin_util::math::vector3::Vector3; + +pub struct EndCrystalEntity { + entity: Entity, +} + +impl EndCrystalEntity { + pub fn new(entity: Entity) -> Self { + Self { entity } + } +} + +impl EndCrystalEntity { + pub async fn set_show_bottom(&self, show_bottom: bool) { + self.entity + .send_meta_data(&[Metadata::new(9, MetaDataType::Boolean, show_bottom)]) + .await; + } +} + +#[async_trait] +impl NBTStorage for EndCrystalEntity { + async fn write_nbt(&self, _nbt: &mut NbtCompound) {} + + async fn read_nbt_non_mut(&self, _nbt: &NbtCompound) {} +} + +#[async_trait] +impl EntityBase for EndCrystalEntity { + fn get_entity(&self) -> &Entity { + &self.entity + } + + fn get_living_entity(&self) -> Option<&LivingEntity> { + None + } + + async fn damage_with_context( + &self, + _caller: Arc, + _amount: f32, + damage_type: DamageType, + _position: Option>, + _source: Option<&dyn EntityBase>, + _cause: Option<&dyn EntityBase>, + ) -> bool { + if damage_type != DamageType::EXPLOSION { + self.entity.world.explode(self.entity.pos.load(), 6.0).await; + } + + // TODO + self.entity.remove().await; + true + } + + fn as_nbt_storage(&self) -> &dyn NBTStorage { + self + } +} diff --git a/pumpkin/src/entity/decoration/mod.rs b/pumpkin/src/entity/decoration/mod.rs index eb8269ee3..93a4c23f2 100644 --- a/pumpkin/src/entity/decoration/mod.rs +++ b/pumpkin/src/entity/decoration/mod.rs @@ -1 +1,2 @@ +pub mod end_crystal; pub mod painting; diff --git a/pumpkin/src/entity/falling.rs b/pumpkin/src/entity/falling.rs index 1d4bec11c..73556becc 100644 --- a/pumpkin/src/entity/falling.rs +++ b/pumpkin/src/entity/falling.rs @@ -1,8 +1,8 @@ use async_trait::async_trait; use pumpkin_data::Block; -use pumpkin_data::{entity::EntityType}; +use pumpkin_data::entity::EntityType; use pumpkin_protocol::java::client::play::{MetaDataType, Metadata}; -use pumpkin_util::math::{position::BlockPos}; +use pumpkin_util::math::position::BlockPos; use pumpkin_world::{BlockStateId, world::BlockFlags}; use std::sync::{Arc, atomic::Ordering}; use uuid::Uuid; diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 8ece63150..bcbc1e80a 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -18,12 +18,12 @@ use crate::world::loot::{LootContextParameters, LootTableExt}; use async_trait::async_trait; use crossbeam::atomic::AtomicCell; use pumpkin_config::advanced_config; +use pumpkin_data::Block; use pumpkin_data::damage::DeathMessageType; use pumpkin_data::data_component_impl::{DeathProtectionImpl, EquipmentSlot, FoodImpl}; use pumpkin_data::effect::StatusEffect; use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType}; use pumpkin_data::sound::SoundCategory; -use pumpkin_data::Block; use pumpkin_data::{damage::DamageType, sound::Sound}; use pumpkin_inventory::entity_equipment::EntityEquipment; use pumpkin_nbt::compound::NbtCompound; @@ -871,6 +871,7 @@ impl LivingEntity { if let Some(player) = caller.get_player() { return player.inventory.held_item(); } + // TODO: this is wrong let slot = self .equipment_slots .get(&PlayerInventory::OFF_HAND_SLOT) diff --git a/pumpkin/src/entity/tnt.rs b/pumpkin/src/entity/tnt.rs index 495d2b8af..55984adba 100644 --- a/pumpkin/src/entity/tnt.rs +++ b/pumpkin/src/entity/tnt.rs @@ -2,7 +2,7 @@ use super::{Entity, EntityBase, NBTStorage, living::LivingEntity}; use crate::server::Server; use async_trait::async_trait; use core::f32; -use pumpkin_data::{Block}; +use pumpkin_data::Block; use pumpkin_protocol::{ codec::var_int::VarInt, java::client::play::{MetaDataType, Metadata}, @@ -65,7 +65,7 @@ impl EntityBase for TNTEntity { self.entity.remove().await; self.entity .world - .explode(server, self.entity.pos.load(), self.power) + .explode(self.entity.pos.load(), self.power) .await; } else { entity.update_fluid_state(&caller).await; diff --git a/pumpkin/src/entity/type.rs b/pumpkin/src/entity/type.rs index 20aa26750..6e58d864c 100644 --- a/pumpkin/src/entity/type.rs +++ b/pumpkin/src/entity/type.rs @@ -5,7 +5,11 @@ use pumpkin_util::math::vector3::Vector3; use uuid::Uuid; use crate::{ - entity::{Entity, EntityBase, decoration::painting::PaintingEntity, mob::zombie::Zombie}, + entity::{ + Entity, EntityBase, + decoration::{end_crystal::EndCrystalEntity, painting::PaintingEntity}, + mob::zombie::Zombie, + }, world::World, }; @@ -21,6 +25,7 @@ pub async fn from_type( let mob: Arc = match entity_type.id { id if id == EntityType::ZOMBIE.id => Zombie::make(entity).await, id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)), + id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)), // TODO _ => Arc::new(entity), // Fallback Entity }; diff --git a/pumpkin/src/item/items/axe.rs b/pumpkin/src/item/items/axe.rs index a60492644..3f9d01cb6 100644 --- a/pumpkin/src/item/items/axe.rs +++ b/pumpkin/src/item/items/axe.rs @@ -5,10 +5,10 @@ use async_trait::async_trait; use pumpkin_data::BlockDirection; use pumpkin_data::block_properties::BlockProperties; use pumpkin_data::block_properties::{OakDoorLikeProperties, PaleOakWoodLikeProperties}; -use pumpkin_data::item::Item; use pumpkin_data::tag::Taggable; use pumpkin_data::{Block, tag}; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; pub struct AxeItem; @@ -24,7 +24,7 @@ impl ItemBehaviour for AxeItem { #[allow(clippy::too_many_lines)] async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, _face: BlockDirection, diff --git a/pumpkin/src/item/items/end_crystal.rs b/pumpkin/src/item/items/end_crystal.rs new file mode 100644 index 000000000..3ccf795fb --- /dev/null +++ b/pumpkin/src/item/items/end_crystal.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use crate::entity::Entity; +use crate::entity::decoration::end_crystal::EndCrystalEntity; +use crate::entity::player::Player; +use crate::item::{ItemBehaviour, ItemMetadata}; +use crate::server::Server; +use async_trait::async_trait; +use pumpkin_data::entity::EntityType; +use pumpkin_data::item::Item; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; +use uuid::Uuid; + +pub struct EndCrystalItem; + +impl ItemMetadata for EndCrystalItem { + fn ids() -> Box<[u16]> { + [Item::END_CRYSTAL.id].into() + } +} + +#[async_trait] +impl ItemBehaviour for EndCrystalItem { + async fn use_on_block( + &self, + item: &mut ItemStack, + player: &Player, + location: BlockPos, + _face: BlockDirection, + _block: &Block, + _server: &Server, + ) { + let world = player.world(); + let block = world.get_block(&location).await; + if block != &Block::OBSIDIAN && block != &Block::BEDROCK { + return; + } + + let location = location.up(); + let entity = Entity::new( + Uuid::new_v4(), + world.clone(), + location.to_f64(), + &EntityType::END_CRYSTAL, + false, + ); + let end_crystal = Arc::new(EndCrystalEntity::new(entity)); + world.spawn_entity(end_crystal.clone()).await; + end_crystal.set_show_bottom(false).await; + item.decrement_unless_creative(player.gamemode.load(), 1); + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/pumpkin/src/item/items/ender_eye.rs b/pumpkin/src/item/items/ender_eye.rs index a56e7d237..f6c59646a 100644 --- a/pumpkin/src/item/items/ender_eye.rs +++ b/pumpkin/src/item/items/ender_eye.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use crate::{server::Server, world::portal::end::EndPortal}; use pumpkin_data::{Block, BlockDirection, item::Item}; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; use crate::item::{ItemBehaviour, ItemMetadata}; @@ -21,7 +22,7 @@ impl ItemMetadata for EnderEyeItem { impl ItemBehaviour for EnderEyeItem { async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, _face: BlockDirection, diff --git a/pumpkin/src/item/items/hoe.rs b/pumpkin/src/item/items/hoe.rs index 0802c6b27..6dc9c7566 100644 --- a/pumpkin/src/item/items/hoe.rs +++ b/pumpkin/src/item/items/hoe.rs @@ -26,7 +26,7 @@ impl ItemMetadata for HoeItem { impl ItemBehaviour for HoeItem { async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, face: BlockDirection, diff --git a/pumpkin/src/item/items/honeycomb.rs b/pumpkin/src/item/items/honeycomb.rs index 97dafd821..312ec695c 100644 --- a/pumpkin/src/item/items/honeycomb.rs +++ b/pumpkin/src/item/items/honeycomb.rs @@ -17,6 +17,7 @@ use pumpkin_data::{Block, tag}; use pumpkin_util::math::position::BlockPos; use pumpkin_world::block::entities::BlockEntity; use pumpkin_world::block::entities::sign::SignBlockEntity; +use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; pub struct HoneyCombItem; @@ -31,7 +32,7 @@ impl ItemMetadata for HoneyCombItem { impl ItemBehaviour for HoneyCombItem { async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, _face: BlockDirection, diff --git a/pumpkin/src/item/items/ignite/fire_charge.rs b/pumpkin/src/item/items/ignite/fire_charge.rs index d275b0266..a65611a17 100644 --- a/pumpkin/src/item/items/ignite/fire_charge.rs +++ b/pumpkin/src/item/items/ignite/fire_charge.rs @@ -7,6 +7,7 @@ use pumpkin_data::item::Item; use pumpkin_data::sound::Sound; use pumpkin_data::sound::SoundCategory; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; use crate::entity::player::Player; @@ -27,7 +28,7 @@ impl ItemMetadata for FireChargeItem { impl ItemBehaviour for FireChargeItem { async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, face: BlockDirection, @@ -44,7 +45,6 @@ impl ItemBehaviour for FireChargeItem { .play_block_sound(Sound::ItemFirechargeUse, SoundCategory::Blocks, pos) .await; }, - _item, player, location, face, diff --git a/pumpkin/src/item/items/ignite/flint_and_steel.rs b/pumpkin/src/item/items/ignite/flint_and_steel.rs index 2688c14fa..bb391b8b0 100644 --- a/pumpkin/src/item/items/ignite/flint_and_steel.rs +++ b/pumpkin/src/item/items/ignite/flint_and_steel.rs @@ -8,6 +8,7 @@ use pumpkin_data::Block; use pumpkin_data::BlockDirection; use pumpkin_data::item::Item; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; use std::sync::Arc; @@ -25,7 +26,7 @@ impl ItemMetadata for FlintAndSteelItem { impl ItemBehaviour for FlintAndSteelItem { async fn use_on_block( &self, - item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, face: BlockDirection, @@ -38,7 +39,6 @@ impl ItemBehaviour for FlintAndSteelItem { .set_block_state(&pos, new_state_id, BlockFlags::NOTIFY_ALL) .await; }, - item, player, location, face, diff --git a/pumpkin/src/item/items/ignite/ignition.rs b/pumpkin/src/item/items/ignite/ignition.rs index b7a69032b..e3b9d3367 100644 --- a/pumpkin/src/item/items/ignite/ignition.rs +++ b/pumpkin/src/item/items/ignite/ignition.rs @@ -4,7 +4,6 @@ use crate::entity::player::Player; use crate::server::Server; use crate::world::World; use pumpkin_data::fluid::Fluid; -use pumpkin_data::item::Item; use pumpkin_data::{Block, BlockDirection}; use pumpkin_util::math::position::BlockPos; use std::sync::Arc; @@ -14,7 +13,6 @@ pub struct Ignition; impl Ignition { pub async fn ignite_block( ignite_logic: F, - _item: &Item, player: &Player, location: BlockPos, face: BlockDirection, diff --git a/pumpkin/src/item/items/minecart.rs b/pumpkin/src/item/items/minecart.rs index e92f3f46a..fa287dea6 100644 --- a/pumpkin/src/item/items/minecart.rs +++ b/pumpkin/src/item/items/minecart.rs @@ -15,6 +15,7 @@ use pumpkin_data::tag::Taggable; use pumpkin_data::{Block, tag}; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; +use pumpkin_world::item::ItemStack; use uuid::Uuid; pub struct MinecartItem; @@ -51,7 +52,7 @@ impl ItemMetadata for MinecartItem { impl ItemBehaviour for MinecartItem { async fn use_on_block( &self, - item: &Item, + item: &mut ItemStack, player: &Player, location: BlockPos, _face: BlockDirection, @@ -74,7 +75,7 @@ impl ItemBehaviour for MinecartItem { .is_ascending() }; let height = if is_ascending { 0.5 } else { 0.0 }; - let entity_type = Self::item_to_entity(item); + let entity_type = Self::item_to_entity(item.item); let pos = location.to_f64(); let entity = Arc::new(Entity::new( Uuid::new_v4(), diff --git a/pumpkin/src/item/items/mod.rs b/pumpkin/src/item/items/mod.rs index 11ccb3bd4..824b5cc36 100644 --- a/pumpkin/src/item/items/mod.rs +++ b/pumpkin/src/item/items/mod.rs @@ -2,6 +2,7 @@ pub mod axe; pub mod bucket; pub mod dye; pub mod egg; +pub mod end_crystal; pub mod ender_eye; pub mod glowing_ink_sac; pub mod hoe; @@ -15,6 +16,7 @@ pub mod snowball; pub mod swords; pub mod trident; +use crate::item::items::end_crystal::EndCrystalItem; use crate::item::items::minecart::MinecartItem; use super::registry::ItemRegistry; @@ -51,6 +53,7 @@ pub fn default_registry() -> Arc { manager.register(FilledBucketItem); manager.register(ShovelItem); manager.register(AxeItem); + manager.register(EndCrystalItem); manager.register(MinecartItem); manager.register(HoneyCombItem); manager.register(EnderEyeItem); diff --git a/pumpkin/src/item/items/shovel.rs b/pumpkin/src/item/items/shovel.rs index 7b73f12df..6383e8b91 100644 --- a/pumpkin/src/item/items/shovel.rs +++ b/pumpkin/src/item/items/shovel.rs @@ -4,11 +4,11 @@ use crate::server::Server; use async_trait::async_trait; use pumpkin_data::BlockDirection; use pumpkin_data::block_properties::{BlockProperties, CampfireLikeProperties}; -use pumpkin_data::item::Item; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::world::WorldEvent; use pumpkin_data::{Block, tag}; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; use pumpkin_world::world::BlockFlags; use rand::{Rng, rng}; @@ -24,7 +24,7 @@ impl ItemMetadata for ShovelItem { impl ItemBehaviour for ShovelItem { async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, player: &Player, location: BlockPos, face: BlockDirection, diff --git a/pumpkin/src/item/mod.rs b/pumpkin/src/item/mod.rs index 250c8e470..882a8eba3 100644 --- a/pumpkin/src/item/mod.rs +++ b/pumpkin/src/item/mod.rs @@ -11,6 +11,7 @@ use pumpkin_data::BlockDirection; use pumpkin_data::item::Item; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; +use pumpkin_world::item::ItemStack; pub trait ItemMetadata { fn ids() -> Box<[u16]>; @@ -22,7 +23,7 @@ pub trait ItemBehaviour: Send + Sync { async fn use_on_block( &self, - _item: &Item, + _item: &mut ItemStack, _player: &Player, _location: BlockPos, _face: BlockDirection, diff --git a/pumpkin/src/item/registry.rs b/pumpkin/src/item/registry.rs index 16c031341..b75e4dc7d 100644 --- a/pumpkin/src/item/registry.rs +++ b/pumpkin/src/item/registry.rs @@ -4,6 +4,7 @@ use pumpkin_data::Block; use pumpkin_data::BlockDirection; use pumpkin_data::item::Item; use pumpkin_util::math::position::BlockPos; +use pumpkin_world::item::ItemStack; use std::collections::HashMap; use std::sync::Arc; @@ -32,17 +33,17 @@ impl ItemRegistry { pub async fn use_on_block( &self, - item: &Item, + stack: &mut ItemStack, player: &Player, location: BlockPos, face: BlockDirection, block: &Block, server: &Server, ) { - let pumpkin_item = self.get_pumpkin_item(item); + let pumpkin_item = self.get_pumpkin_item(stack.item); if let Some(pumpkin_item) = pumpkin_item { pumpkin_item - .use_on_block(item, player, location, face, block, server) + .use_on_block(stack, player, location, face, block, server) .await; } } diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index d093ba9ec..7fdde2c34 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -1432,20 +1432,14 @@ impl JavaClient { return Ok(()); } + let mut stack = item.lock().await; server .item_registry - .use_on_block( - item.lock().await.item, - player, - position, - face, - block, - server, - ) + .use_on_block(&mut stack, player, position, face, block, server) .await; // Check if the item is a block, because not every item can be placed :D - let item_id = item.lock().await.item.id; + let item_id = stack.item.id; if let Some(block) = Block::from_item_id(item_id) { should_try_decrement = self .run_is_block_place(player, block, server, use_item_on, position, face) @@ -1453,7 +1447,7 @@ impl JavaClient { } // Check if the item is a spawn egg - let item_id = item.lock().await.item.id; + let item_id = stack.item.id; if let Some(entity) = entity_from_egg(item_id) { self.spawn_entity_from_egg(player, entity, position, face) .await; @@ -1464,7 +1458,7 @@ impl JavaClient { // TODO: Config // Decrease block count if player.gamemode.load() != GameMode::Creative { - item.lock().await.decrement(1); + stack.decrement(1); } } diff --git a/pumpkin/src/world/explosion.rs b/pumpkin/src/world/explosion.rs index bd34919f3..7f8243e59 100644 --- a/pumpkin/src/world/explosion.rs +++ b/pumpkin/src/world/explosion.rs @@ -6,7 +6,6 @@ use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use crate::{ block::{ExplodeArgs, drop_loot}, - server::Server, world::loot::LootContextParameters, }; @@ -74,14 +73,14 @@ impl Explosion { map } - pub async fn explode(&self, server: &Server, world: &Arc) { + pub async fn explode(&self, world: &Arc) { let blocks = self.get_blocks_to_destroy(world).await; // TODO: Entity damage, fire for (pos, (block, state)) in blocks { if state.is_air() { continue; } - let pumpkin_block = server.block_registry.get_pumpkin_block(block); + let pumpkin_block = world.block_registry.get_pumpkin_block(block); world.set_block_state(&pos, 0, BlockFlags::NOTIFY_ALL).await; diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index da5f1fbaf..b1ff85e81 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1726,9 +1726,9 @@ impl World { player.set_health(20.0).await; } - pub async fn explode(self: &Arc, server: &Server, position: Vector3, power: f32) { + pub async fn explode(self: &Arc, position: Vector3, power: f32) { let explosion = Explosion::new(power, position); - explosion.explode(server, self).await; + explosion.explode(self).await; let particle = if power < 2.0 { Particle::Explosion } else {