refactor(entities): from async to sync Part 2

This commit is contained in:
Alexander Medvedev 2026-08-26 16:45:10 +02:00
parent 8ee11b06ce
commit 5d841d6d3b
209 changed files with 7557 additions and 7959 deletions

View File

@ -10,6 +10,7 @@
//! Ender chests track when players open and close them to properly //! Ender chests track when players open and close them to properly
//! manage the viewer count for animation purposes. //! manage the viewer count for animation purposes.
use std::sync::{Mutex, RwLock};
use std::{any::Any, pin::Pin, sync::Arc}; use std::{any::Any, pin::Pin, sync::Arc};
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
@ -17,7 +18,6 @@ use pumpkin_world::{
block::viewer::ViewerCountTracker, block::viewer::ViewerCountTracker,
inventory::{Clearable, Inventory, InventoryFuture}, inventory::{Clearable, Inventory, InventoryFuture},
}; };
use tokio::sync::{Mutex, RwLock};
/// A player's ender chest inventory. /// A player's ender chest inventory.
/// ///
@ -55,21 +55,33 @@ impl EnderChestInventory {
/// Sets the viewer count tracker for this inventory. /// Sets the viewer count tracker for this inventory.
/// ///
/// Used to animate the ender chest lid based on viewers. /// Used to animate the ender chest lid based on viewers.
pub async fn set_tracker(&self, tracker: Arc<ViewerCountTracker>) { pub fn set_tracker(&self, tracker: Arc<ViewerCountTracker>) {
let old = self.tracker.lock().await.replace(tracker); let old = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.replace(tracker);
if let Some(old_tracker) = old { if let Some(old_tracker) = old {
old_tracker.close_container(); old_tracker.close_container();
} }
} }
/// Checks if this inventory has a tracker set. /// Checks if this inventory has a tracker set.
pub async fn has_tracker(&self) -> bool { pub fn has_tracker(&self) -> bool {
self.tracker.lock().await.is_some() self.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_some()
} }
/// Checks if the given tracker is associated with this inventory. /// Checks if the given tracker is associated with this inventory.
pub async fn is_tracker(&self, tracker: &Arc<ViewerCountTracker>) -> bool { pub fn is_tracker(&self, tracker: &Arc<ViewerCountTracker>) -> bool {
if let Some(value) = self.tracker.lock().await.as_ref() { if let Some(value) = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
return Arc::ptr_eq(value, tracker); return Arc::ptr_eq(value, tracker);
} }
false false
@ -83,14 +95,20 @@ impl Inventory for EnderChestInventory {
fn is_empty(&self) -> InventoryFuture<'_, bool> { fn is_empty(&self) -> InventoryFuture<'_, bool> {
Box::pin(async move { Box::pin(async move {
let items = self.items.read().await; let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.iter().all(ItemStack::is_empty) items.iter().all(ItemStack::is_empty)
}) })
} }
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let items = self.items.read().await; let items = self
.items
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items items
.get(slot) .get(slot)
.cloned() .cloned()
@ -100,7 +118,10 @@ impl Inventory for EnderChestInventory {
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> { fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut items = self.items.write().await; let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE { if slot < Self::INVENTORY_SIZE {
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone()) std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
} else { } else {
@ -111,7 +132,10 @@ impl Inventory for EnderChestInventory {
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> { fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
Box::pin(async move { Box::pin(async move {
let mut items = self.items.write().await; let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE && !items[slot].is_empty() && amount > 0 { if slot < Self::INVENTORY_SIZE && !items[slot].is_empty() && amount > 0 {
items[slot].split(amount) items[slot].split(amount)
} else { } else {
@ -122,7 +146,10 @@ impl Inventory for EnderChestInventory {
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> { fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
let mut items = self.items.write().await; let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if slot < Self::INVENTORY_SIZE { if slot < Self::INVENTORY_SIZE {
items[slot] = stack; items[slot] = stack;
} }
@ -131,7 +158,12 @@ impl Inventory for EnderChestInventory {
fn on_open(&self) -> InventoryFuture<'_, ()> { fn on_open(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
if let Some(tracker) = self.tracker.lock().await.as_ref() { if let Some(tracker) = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.as_ref()
{
tracker.open_container(); tracker.open_container();
} }
}) })
@ -139,7 +171,11 @@ impl Inventory for EnderChestInventory {
fn on_close(&self) -> InventoryFuture<'_, ()> { fn on_close(&self) -> InventoryFuture<'_, ()> {
Box::pin(async move { Box::pin(async move {
let tracker = self.tracker.lock().await.take(); let tracker = self
.tracker
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.take();
if let Some(tracker) = tracker { if let Some(tracker) = tracker {
tracker.close_container(); tracker.close_container();
} }
@ -156,7 +192,10 @@ impl Inventory for EnderChestInventory {
impl Clearable for EnderChestInventory { impl Clearable for EnderChestInventory {
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> { fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move { Box::pin(async move {
let mut items = self.items.write().await; let mut items = self
.items
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
items.fill_with(|| ItemStack::EMPTY.clone()); items.fill_with(|| ItemStack::EMPTY.clone());
}) })
} }
@ -222,24 +261,24 @@ mod tests {
let tracker1 = Arc::new(ViewerCountTracker::new()); let tracker1 = Arc::new(ViewerCountTracker::new());
let tracker2 = Arc::new(ViewerCountTracker::new()); let tracker2 = Arc::new(ViewerCountTracker::new());
ec.set_tracker(tracker1.clone()).await; ec.set_tracker(tracker1.clone());
assert!(ec.has_tracker().await); assert!(ec.has_tracker());
assert!(ec.is_tracker(&tracker1).await); assert!(ec.is_tracker(&tracker1));
assert!(!ec.is_tracker(&tracker2).await); assert!(!ec.is_tracker(&tracker2));
ec.on_open().await; ec.on_open().await;
assert_eq!(tracker1.get_viewer_count(), 1); assert_eq!(tracker1.get_viewer_count(), 1);
// Setting a new tracker while one is open should close the old tracker // Setting a new tracker while one is open should close the old tracker
ec.set_tracker(tracker2.clone()).await; ec.set_tracker(tracker2.clone());
assert_eq!(tracker1.get_viewer_count(), 0); assert_eq!(tracker1.get_viewer_count(), 0);
assert!(ec.is_tracker(&tracker2).await); assert!(ec.is_tracker(&tracker2));
ec.on_open().await; ec.on_open().await;
assert_eq!(tracker2.get_viewer_count(), 1); assert_eq!(tracker2.get_viewer_count(), 1);
ec.on_close().await; ec.on_close().await;
assert_eq!(tracker2.get_viewer_count(), 0); assert_eq!(tracker2.get_viewer_count(), 0);
assert!(!ec.has_tracker().await); assert!(!ec.has_tracker());
} }
} }

View File

@ -404,6 +404,40 @@ impl PlayerInventory {
inv.swap(selected, slot); inv.swap(selected, slot);
} }
/// Gets the item in the specified slot (synchronously).
pub fn get_slot(&self, slot: usize) -> ItemStack {
if slot < Self::MAIN_SIZE {
let inv = self
.main_inventory
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot].clone()
} else if let Some(slot_type) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(slot_type)
} else {
ItemStack::EMPTY.clone()
}
}
/// Sets the item in the specified slot (synchronously).
pub fn set_slot(&self, slot: usize, stack: ItemStack) {
if slot < Self::MAIN_SIZE {
let mut inv = self
.main_inventory
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
inv[slot] = stack;
} else if let Some(slot_type) = self.equipment_slots.get(&slot) {
self.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.put(slot_type, stack);
}
}
/// Gives a stack to the player or drops it if inventory is full. /// Gives a stack to the player or drops it if inventory is full.
pub async fn offer_or_drop_stack(&self, stack: ItemStack, player: &dyn InventoryPlayer) { pub async fn offer_or_drop_stack(&self, stack: ItemStack, player: &dyn InventoryPlayer) {
self.offer(stack, true, player).await; self.offer(stack, true, player).await;

View File

@ -233,13 +233,11 @@ impl BedBlock {
world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS); world.break_block(&bed_head_pos, None, BlockFlags::SKIP_DROPS);
world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS); world.break_block(&bed_foot_pos, None, BlockFlags::SKIP_DROPS);
world world.explode(
.explode( bed_head_pos.to_centered_f64(),
bed_head_pos.to_centered_f64(), 5.0,
5.0, crate::world::ExplosionInteraction::Block,
crate::world::ExplosionInteraction::Block, );
)
.await;
return BlockActionResult::SuccessServer; return BlockActionResult::SuccessServer;
} }

View File

@ -47,6 +47,18 @@ fn fire_cauldron_change(
!event.cancelled !event.cancelled
} }
fn give_item_or_drop(
player: &crate::entity::player::Player,
world: &std::sync::Arc<crate::world::World>,
item: &'static Item,
) {
let mut stack = ItemStack::new(1, item);
let was_added = player.inventory.insert_stack_anywhere(&mut stack);
if !was_added && !stack.is_empty() {
world.drop_stack(&player.position().to_block_pos(), stack);
}
}
impl BlockBehaviour for CauldronBlock { impl BlockBehaviour for CauldronBlock {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult { fn use_with_item(&self, args: UseWithItemArgs<'_>) -> BlockActionResult {
@ -78,13 +90,7 @@ impl BlockBehaviour for CauldronBlock {
&args.position.to_f64(), &args.position.to_f64(),
); );
args.item_stack.decrement_unless_creative(gamemode, 1); args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player); give_item_or_drop(args.player, args.world, &Item::BUCKET);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
.await;
});
return BlockActionResult::Success; return BlockActionResult::Success;
} else if item_id == Item::LAVA_BUCKET.id { } else if item_id == Item::LAVA_BUCKET.id {
args.world.set_block_state( args.world.set_block_state(
@ -98,13 +104,7 @@ impl BlockBehaviour for CauldronBlock {
&args.position.to_f64(), &args.position.to_f64(),
); );
args.item_stack.decrement_unless_creative(gamemode, 1); args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player); give_item_or_drop(args.player, args.world, &Item::BUCKET);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
.await;
});
return BlockActionResult::Success; return BlockActionResult::Success;
} else if item_id == Item::POWDER_SNOW_BUCKET.id { } else if item_id == Item::POWDER_SNOW_BUCKET.id {
let state_id = Block::POWDER_SNOW_CAULDRON let state_id = Block::POWDER_SNOW_CAULDRON
@ -118,13 +118,7 @@ impl BlockBehaviour for CauldronBlock {
&args.position.to_f64(), &args.position.to_f64(),
); );
args.item_stack.decrement_unless_creative(gamemode, 1); args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player); give_item_or_drop(args.player, args.world, &Item::BUCKET);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, &Item::BUCKET), player.as_ref())
.await;
});
return BlockActionResult::Success; return BlockActionResult::Success;
} else if item_id == Item::POTION.id { } else if item_id == Item::POTION.id {
let state_id = Block::WATER_CAULDRON let state_id = Block::WATER_CAULDRON
@ -138,16 +132,7 @@ impl BlockBehaviour for CauldronBlock {
&args.position.to_f64(), &args.position.to_f64(),
); );
args.item_stack.decrement_unless_creative(gamemode, 1); args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player); give_item_or_drop(args.player, args.world, &Item::GLASS_BOTTLE);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(
ItemStack::new(1, &Item::GLASS_BOTTLE),
player.as_ref(),
)
.await;
});
return BlockActionResult::Success; return BlockActionResult::Success;
} }
} }
@ -187,13 +172,7 @@ impl BlockBehaviour for CauldronBlock {
args.world args.world
.play_sound(sound, SoundCategory::Blocks, &args.position.to_f64()); .play_sound(sound, SoundCategory::Blocks, &args.position.to_f64());
args.item_stack.decrement_unless_creative(gamemode, 1); args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player); give_item_or_drop(args.player, args.world, result_item);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(ItemStack::new(1, result_item), player.as_ref())
.await;
});
return BlockActionResult::Success; return BlockActionResult::Success;
} }
} }
@ -218,16 +197,7 @@ impl BlockBehaviour for CauldronBlock {
&args.position.to_f64(), &args.position.to_f64(),
); );
args.item_stack.decrement_unless_creative(gamemode, 1); args.item_stack.decrement_unless_creative(gamemode, 1);
let player = Arc::clone(args.player); give_item_or_drop(args.player, args.world, &Item::GLASS_BOTTLE);
tokio::spawn(async move {
player
.inventory
.offer_or_drop_stack(
ItemStack::new(1, &Item::GLASS_BOTTLE),
player.as_ref(),
)
.await;
});
return BlockActionResult::Success; return BlockActionResult::Success;
} }
} }

View File

@ -35,7 +35,7 @@ impl ScreenHandlerFactory for EnderChestScreenFactory {
) -> BoxFuture<'a, Option<SharedScreenHandler>> { ) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move { Box::pin(async move {
if let Some(tracker) = &self.tracker { if let Some(tracker) = &self.tracker {
self.inventory.set_tracker(tracker.clone()).await; self.inventory.set_tracker(tracker.clone());
} }
let handler = let handler =
create_generic_9x3(sync_id, player_inventory, self.inventory.clone()).await; create_generic_9x3(sync_id, player_inventory, self.inventory.clone()).await;

View File

@ -41,11 +41,7 @@ impl crate::block::BlockBehaviour for PumpkinBlock {
ItemStack::new(4, &Item::PUMPKIN_SEEDS), ItemStack::new(4, &Item::PUMPKIN_SEEDS),
)); ));
args.world.spawn_entity(item_entity); args.world.spawn_entity(item_entity);
let player = Arc::clone(args.player); args.player.damage_item_in_slot(args.equipment_slot, 1);
let slot = args.equipment_slot.clone();
tokio::spawn(async move {
player.damage_item_in_slot(&slot, 1).await;
});
BlockActionResult::Consume BlockActionResult::Consume
} }
} }

View File

@ -28,7 +28,11 @@ impl BlockBehaviour for DaylightDetectorBlock {
} }
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
let player_abilities = args.player.abilities.blocking_lock(); let player_abilities = args
.player
.abilities
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !player_abilities.allow_modify_world { if !player_abilities.allow_modify_world {
return BlockActionResult::Pass; return BlockActionResult::Pass;
} }

View File

@ -669,7 +669,7 @@ impl DispenserBlock {
item: &mut ItemStack, item: &mut ItemStack,
) { ) {
let front = Self::target_position(ctx); let front = Self::target_position(ctx);
let Some(filled) = try_pickup_fluid_at(ctx.world, front).await else { let Some(filled) = try_pickup_fluid_at(ctx.world, front) else {
Self::drop_item(ctx, item); Self::drop_item(ctx, item);
return; return;
}; };
@ -717,7 +717,6 @@ impl DispenserBlock {
*ctx.position, *ctx.position,
ctx.facing.to_block_direction(), ctx.facing.to_block_direction(),
) )
.await
}; };
if emptied { if emptied {
@ -737,7 +736,7 @@ impl DispenserBlock {
true true
} else { } else {
Ignition::ignite_block( Ignition::ignite_block(
|world: Arc<World>, pos: BlockPos, new_state_id: BlockStateId| async move { |world: Arc<World>, pos: BlockPos, new_state_id: BlockStateId| {
world.set_block_state(&pos, new_state_id, BlockFlags::NOTIFY_ALL); world.set_block_state(&pos, new_state_id, BlockFlags::NOTIFY_ALL);
}, },
ctx.world, ctx.world,
@ -745,7 +744,6 @@ impl DispenserBlock {
front, front,
front_block, front_block,
) )
.await
}; };
if ignited { if ignited {
@ -761,7 +759,7 @@ impl DispenserBlock {
let front = Self::target_position(ctx); let front = Self::target_position(ctx);
let front_block = ctx.world.get_block(&front); let front_block = ctx.world.get_block(&front);
if try_wax_block(ctx.world, front, front_block).await { if try_wax_block(ctx.world, front, front_block) {
item.decrement(1); item.decrement(1);
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense); Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
} else { } else {

View File

@ -54,13 +54,9 @@ impl BlockBehaviour for RespawnAnchorBlock {
if args.world.dimension != Dimension::THE_NETHER { if args.world.dimension != Dimension::THE_NETHER {
args.world args.world
.break_block(args.position, None, BlockFlags::SKIP_DROPS); .break_block(args.position, None, BlockFlags::SKIP_DROPS);
let world = Arc::clone(args.world);
let center_pos = args.position.to_centered_f64(); let center_pos = args.position.to_centered_f64();
tokio::spawn(async move { args.world
world .explode(center_pos, 5.0, crate::world::ExplosionInteraction::Block);
.explode(center_pos, 5.0, crate::world::ExplosionInteraction::Block)
.await;
});
return BlockActionResult::SuccessServer; return BlockActionResult::SuccessServer;
} }

View File

@ -72,12 +72,7 @@ pub trait FlowingFluid: Send + Sync {
/// 3. Triggering fluid spread to adjacent positions /// 3. Triggering fluid spread to adjacent positions
/// ///
/// Sources (level 8, non-falling) always spread without state changes. /// Sources (level 8, non-falling) always spread without state changes.
async fn on_scheduled_tick_internal( fn on_scheduled_tick_internal(&self, world: &Arc<World>, fluid: &Fluid, block_pos: &BlockPos) {
&self,
world: &Arc<World>,
fluid: &Fluid,
block_pos: &BlockPos,
) {
let current_block_state_id = world.get_block_state_id(block_pos); let current_block_state_id = world.get_block_state_id(block_pos);
let block = Block::from_state_id(current_block_state_id); let block = Block::from_state_id(current_block_state_id);
@ -96,7 +91,7 @@ pub trait FlowingFluid: Send + Sync {
// Update state if non-source // Update state if non-source
if !is_source && !waterlogged { if !is_source && !waterlogged {
let new_fluid_state = self.get_new_liquid(world, fluid, block_pos).await; let new_fluid_state = self.get_new_liquid(world, fluid, block_pos);
if let Some(new_state) = new_fluid_state { if let Some(new_state) = new_fluid_state {
let new_state_id = new_state.to_state_id(fluid); let new_state_id = new_state.to_state_id(fluid);
@ -127,8 +122,7 @@ pub trait FlowingFluid: Send + Sync {
} }
// Then, spread using the appropriate state // Then, spread using the appropriate state
self.try_flow(world, fluid, block_pos, &state_for_spreading) self.try_flow(world, fluid, block_pos, &state_for_spreading);
.await;
} }
/// Attempts to flow fluid from a position, prioritizing downward flow. /// Attempts to flow fluid from a position, prioritizing downward flow.
@ -138,7 +132,7 @@ pub trait FlowingFluid: Send + Sync {
/// 2. Sides - spread horizontally using pathfinding /// 2. Sides - spread horizontally using pathfinding
/// ///
/// Sources with 3+ adjacent sources also spread to sides when flowing down. /// Sources with 3+ adjacent sources also spread to sides when flowing down.
async fn try_flow( fn try_flow(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -153,24 +147,23 @@ pub trait FlowingFluid: Send + Sync {
// Try to flow down first // Try to flow down first
if is_hole { if is_hole {
let falling_props = self.get_flowing(fluid, Level::L8, true); let falling_props = self.get_flowing(fluid, Level::L8, true);
self.spread_to(world, fluid, &below_pos, falling_props.to_state_id(fluid)) self.spread_to(world, fluid, &below_pos, falling_props.to_state_id(fluid));
.await;
// Check if we should also spread to sides // Check if we should also spread to sides
if props.level == Level::L8 && props.falling == Falling::False { if props.level == Level::L8 && props.falling == Falling::False {
let source_count = self.count_source_neighbors(world, fluid, block_pos).await; let source_count = self.count_source_neighbors(world, fluid, block_pos);
if source_count >= 3 { if source_count >= 3 {
self.flow_to_sides(world, fluid, block_pos, props).await; self.flow_to_sides(world, fluid, block_pos, props);
} }
} }
return; return;
} }
// Check if fluid should flow to the side(s) // Check if fluid should flow to the side(s)
self.flow_to_sides(world, fluid, block_pos, props).await; self.flow_to_sides(world, fluid, block_pos, props);
} }
async fn count_source_neighbors( fn count_source_neighbors(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -205,7 +198,7 @@ pub trait FlowingFluid: Send + Sync {
/// ///
/// # Returns /// # Returns
/// New fluid properties, or None if fluid should drain /// New fluid properties, or None if fluid should drain
async fn get_new_liquid( fn get_new_liquid(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -295,7 +288,7 @@ pub trait FlowingFluid: Send + Sync {
/// - Fluid tick scheduling for non-source blocks /// - Fluid tick scheduling for non-source blocks
/// ///
/// Called by `spread_to` implementations after fluid-specific pre-checks. /// Called by `spread_to` implementations after fluid-specific pre-checks.
async fn apply_spread( fn apply_spread(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -318,9 +311,7 @@ pub trait FlowingFluid: Send + Sync {
// Check for infinite source formation before quiescence checks // Check for infinite source formation before quiescence checks
if !current_is_source && self.can_convert_to_source(world) { if !current_is_source && self.can_convert_to_source(world) {
let should_convert = self let should_convert = self.check_infinite_source_formation(world, fluid, pos);
.check_infinite_source_formation(world, fluid, pos)
.await;
if should_convert { if should_convert {
let source_props = self.get_source(fluid, false); let source_props = self.get_source(fluid, false);
@ -355,7 +346,7 @@ pub trait FlowingFluid: Send + Sync {
&pumpkin_data::Block::WATER, &pumpkin_data::Block::WATER,
); );
if let Some(server) = world.server.upgrade() { if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire_blocking(&server, &mut event);
} }
if event.cancelled { if event.cancelled {
return; return;
@ -365,9 +356,7 @@ pub trait FlowingFluid: Send + Sync {
// Check for infinite source formation after placing new fluid // Check for infinite source formation after placing new fluid
if self.can_convert_to_source(world) { if self.can_convert_to_source(world) {
let should_convert = self let should_convert = self.check_infinite_source_formation(world, fluid, pos);
.check_infinite_source_formation(world, fluid, pos)
.await;
if should_convert { if should_convert {
let source_props = self.get_source(fluid, false); let source_props = self.get_source(fluid, false);
@ -396,7 +385,7 @@ pub trait FlowingFluid: Send + Sync {
/// ///
/// # Returns /// # Returns
/// `true` if position should convert to a source block /// `true` if position should convert to a source block
async fn check_infinite_source_formation( fn check_infinite_source_formation(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -444,23 +433,16 @@ pub trait FlowingFluid: Send + Sync {
/// ///
/// Default implementation delegates to `apply_spread`. Implementations like /// Default implementation delegates to `apply_spread`. Implementations like
/// lava can override to add fluid-specific logic (e.g., water -> stone conversion). /// lava can override to add fluid-specific logic (e.g., water -> stone conversion).
async fn spread_to( fn spread_to(&self, world: &Arc<World>, fluid: &Fluid, pos: &BlockPos, state_id: BlockStateId) {
&self,
world: &Arc<World>,
fluid: &Fluid,
pos: &BlockPos,
state_id: BlockStateId,
) {
let new_props = FlowingFluidProperties::from_state_id(state_id, fluid); let new_props = FlowingFluidProperties::from_state_id(state_id, fluid);
self.apply_spread(world, fluid, pos, state_id, new_props) self.apply_spread(world, fluid, pos, state_id, new_props);
.await;
} }
/// Spreads fluid horizontally to adjacent positions using pathfinding. /// Spreads fluid horizontally to adjacent positions using pathfinding.
/// ///
/// Uses `get_spread` to find optimal flow directions (shortest distance to holes) /// Uses `get_spread` to find optimal flow directions (shortest distance to holes)
/// and the computed fluid state for each target position. /// and the computed fluid state for each target position.
async fn flow_to_sides( fn flow_to_sides(
&self, &self,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -479,12 +461,12 @@ pub trait FlowingFluid: Send + Sync {
return; return;
} }
let (spread_dirs, count) = pathfinder::get_spread(self, world, fluid, block_pos).await; let (spread_dirs, count) = pathfinder::get_spread(self, world, fluid, block_pos);
for &(direction, state_id) in spread_dirs.iter().take(count) { for &(direction, state_id) in spread_dirs.iter().take(count) {
let side_pos = block_pos.offset(direction.to_offset()); let side_pos = block_pos.offset(direction.to_offset());
self.spread_to(world, fluid, &side_pos, state_id).await; self.spread_to(world, fluid, &side_pos, state_id);
} }
} }
} }

View File

@ -6,7 +6,6 @@ use crate::{
}; };
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, BlockStateId, Block, BlockDirection, BlockState, BlockStateId,
block_properties::blocks_movement,
damage::DamageType, damage::DamageType,
dimension::Dimension, dimension::Dimension,
fluid::{Falling, Fluid, FluidProperties, Level}, fluid::{Falling, Fluid, FluidProperties, Level},
@ -160,12 +159,7 @@ impl FluidBehaviour for FlowingLava {
} }
fn on_scheduled_tick(&self, world: &Arc<World>, _fluid: &Fluid, block_pos: &BlockPos) { fn on_scheduled_tick(&self, world: &Arc<World>, _fluid: &Fluid, block_pos: &BlockPos) {
let world = world.clone(); Self.on_scheduled_tick_internal(world, &Fluid::FLOWING_LAVA, block_pos);
let block_pos = *block_pos;
tokio::spawn(async move {
Self.on_scheduled_tick_internal(&world, &Fluid::FLOWING_LAVA, &block_pos)
.await;
});
} }
fn on_neighbor_update( fn on_neighbor_update(
@ -193,68 +187,58 @@ impl FluidBehaviour for FlowingLava {
} }
fn random_tick(&self, _fluid: &Fluid, world: &Arc<World>, block_pos: &BlockPos) { fn random_tick(&self, _fluid: &Fluid, world: &Arc<World>, block_pos: &BlockPos) {
let world = world.clone(); if !Self::can_spread_fire_around(world, block_pos) {
let block_pos = *block_pos; return;
tokio::spawn(async move { }
if !Self::can_spread_fire_around(&world, &block_pos) {
return;
}
let passes = rand::random_range(0..3); let passes = rand::random_range(0..3);
if passes > 0 { if passes > 0 {
let mut test_pos = block_pos; let mut test_pos = *block_pos;
for _ in 0..passes { for _ in 0..passes {
test_pos = test_pos.offset(Vector3::new( test_pos = test_pos.offset(Vector3::new(
rand::random_range(-1..=1), rand::random_range(-1..=1),
1, 1,
rand::random_range(-1..=1), rand::random_range(-1..=1),
)); ));
if !world.is_loaded(&test_pos) { let (block, _) = world.get_block_and_state_id(&test_pos);
if block.id == Block::AIR.id {
if Self::has_flammable_neighbours(world, &test_pos) {
world.set_block_state(
&test_pos,
Block::FIRE.default_state.id,
BlockFlags::NOTIFY_ALL,
);
return; return;
} }
} else if block.is_solid() {
let Some(block_state) = world.get_block_state_if_loaded(&test_pos) else { return;
return;
};
if block_state.is_air() {
if Self::has_flammable_neighbours(&world, &test_pos) {
Self::ignite_fire_if_possible(&world, &test_pos);
return;
}
} else if blocks_movement(block_state, block_state.id.to_block_id()) {
return;
}
}
} else {
for _ in 0..3 {
let test_pos = block_pos.offset(Vector3::new(
rand::random_range(-1..=1),
0,
rand::random_range(-1..=1),
));
if !world.is_loaded(&test_pos) {
return;
}
let above_pos = test_pos.up();
if !world.is_loaded(&above_pos) {
return;
}
if world
.get_block_state_if_loaded(&above_pos)
.is_some_and(BlockState::is_air)
&& Self::is_flammable(&world, &test_pos)
{
Self::ignite_fire_if_possible(&world, &above_pos);
}
} }
} }
}); } else {
for _ in 0..3 {
let test_pos = block_pos.offset(Vector3::new(
rand::random_range(-1..=1),
0,
rand::random_range(-1..=1),
));
if !world.is_loaded(&test_pos) {
return;
}
let above_pos = test_pos.up();
if world
.get_block_state_if_loaded(&above_pos)
.is_some_and(BlockState::is_air)
&& Self::is_flammable(world, &test_pos)
{
Self::ignite_fire_if_possible(world, &above_pos);
}
}
}
} }
} }
@ -291,13 +275,7 @@ impl FlowingFluid for FlowingLava {
world.level_info.load().game_rules.lava_source_conversion world.level_info.load().game_rules.lava_source_conversion
} }
async fn spread_to( fn spread_to(&self, world: &Arc<World>, fluid: &Fluid, pos: &BlockPos, state_id: BlockStateId) {
&self,
world: &Arc<World>,
fluid: &Fluid,
pos: &BlockPos,
state_id: BlockStateId,
) {
let new_props = FlowingFluidProperties::from_state_id(state_id, fluid); let new_props = FlowingFluidProperties::from_state_id(state_id, fluid);
let current_state_id = world.get_block_state_id(pos); let current_state_id = world.get_block_state_id(pos);
let block = Block::from_state_id(current_state_id); let block = Block::from_state_id(current_state_id);
@ -317,7 +295,6 @@ impl FlowingFluid for FlowingLava {
} }
// Delegate quiescence, replacement and scheduling to the shared helper // Delegate quiescence, replacement and scheduling to the shared helper
self.apply_spread(world, fluid, pos, state_id, new_props) self.apply_spread(world, fluid, pos, state_id, new_props);
.await;
} }
} }

View File

@ -30,7 +30,7 @@ fn is_hole(world: &Arc<World>, fluid: &Fluid, pos: &BlockPos) -> bool {
/// - Holes (downward flow opportunities) get distance 0 priority /// - Holes (downward flow opportunities) get distance 0 priority
/// - All directions with equal minimum distance are returned /// - All directions with equal minimum distance are returned
/// - Returns up to 4 directions with their computed fluid states /// - Returns up to 4 directions with their computed fluid states
pub async fn get_spread<T: FlowingFluid + Sync + ?Sized>( pub fn get_spread<T: FlowingFluid + Sync + ?Sized>(
fluid_impl: &T, fluid_impl: &T,
world: &Arc<World>, world: &Arc<World>,
fluid: &Fluid, fluid: &Fluid,
@ -63,7 +63,7 @@ pub async fn get_spread<T: FlowingFluid + Sync + ?Sized>(
} }
// Skip if no valid fluid state for this position // Skip if no valid fluid state for this position
let Some(new_fluid_props) = fluid_impl.get_new_liquid(world, fluid, &side_pos).await else { let Some(new_fluid_props) = fluid_impl.get_new_liquid(world, fluid, &side_pos) else {
continue; continue;
}; };

View File

@ -36,12 +36,7 @@ impl FluidBehaviour for FlowingWater {
} }
fn on_scheduled_tick(&self, world: &Arc<World>, _fluid: &Fluid, block_pos: &BlockPos) { fn on_scheduled_tick(&self, world: &Arc<World>, _fluid: &Fluid, block_pos: &BlockPos) {
let world = world.clone(); Self.on_scheduled_tick_internal(world, &Fluid::FLOWING_WATER, block_pos);
let block_pos = *block_pos;
tokio::spawn(async move {
Self.on_scheduled_tick_internal(&world, &Fluid::FLOWING_WATER, &block_pos)
.await;
});
} }
fn on_neighbor_update( fn on_neighbor_update(

View File

@ -401,7 +401,7 @@ pub struct BlockEvent {
pub data: u8, pub data: u8,
} }
pub async fn drop_loot( pub fn drop_loot(
world: &Arc<World>, world: &Arc<World>,
block: &Block, block: &Block,
pos: &BlockPos, pos: &BlockPos,
@ -419,7 +419,7 @@ pub async fn drop_loot(
cancelled: false, cancelled: false,
}; };
if let Some(server) = world.server.upgrade() { if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire_blocking(&server, &mut event);
} }
if !event.cancelled { if !event.cancelled {
for stack in event.items { for stack in event.items {
@ -440,7 +440,7 @@ pub async fn drop_loot(
exp: amount, exp: amount,
}; };
if let Some(server) = world.server.upgrade() { if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire_blocking(&server, &mut event);
} }
if event.exp > 0 { if event.exp > 0 {
ExperienceOrbEntity::spawn(world, pos.to_f64(), event.exp as u32); ExperienceOrbEntity::spawn(world, pos.to_f64(), event.exp as u32);

View File

@ -521,7 +521,8 @@ impl EntitySelectorPredicate {
let has_tag = entity let has_tag = entity
.get_entity() .get_entity()
.scoreboard_tags .scoreboard_tags
.blocking_lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.contains(expected_tag); .contains(expected_tag);
has_tag ^ invert has_tag ^ invert
} }
@ -567,8 +568,7 @@ impl EntitySelectorPredicate {
} }
Self::Nbt(expected_nbt, invert) => { Self::Nbt(expected_nbt, invert) => {
let mut actual_nbt = NbtCompound::default(); let mut actual_nbt = NbtCompound::default();
// write_nbt is asynchronous, so we can poll it synchronously because it does not do IO. entity.write_nbt(&mut actual_nbt);
futures::executor::block_on(entity.write_nbt(&mut actual_nbt));
matches_nbt_compound(expected_nbt, &actual_nbt) ^ invert matches_nbt_compound(expected_nbt, &actual_nbt) ^ invert
} }
Self::Predicate(_predicate_id, invert) => { Self::Predicate(_predicate_id, invert) => {

View File

@ -314,7 +314,7 @@ async fn perform(
} }
.create_without_context_args_slice(&[ .create_without_context_args_slice(&[
first_advancement.name(), first_advancement.name(),
first_player.get_display_name().await, first_player.get_display_name(),
])) ]))
} else { } else {
Err(match action { Err(match action {
@ -333,7 +333,7 @@ async fn perform(
} }
.create_without_context_args_slice(&[ .create_without_context_args_slice(&[
TextComponent::text(advancements.len().to_string()), TextComponent::text(advancements.len().to_string()),
first_player.get_display_name().await, first_player.get_display_name(),
])) ]))
} else { } else {
Err(match action { Err(match action {
@ -350,10 +350,7 @@ async fn perform(
if let [first_player] = targets { if let [first_player] = targets {
TextComponent::translate( TextComponent::translate(
format!("{}.one.to.one.success", action.get_key()), format!("{}.one.to.one.success", action.get_key()),
[ [first_advancement.name(), first_player.get_display_name()],
first_advancement.name(),
first_player.get_display_name().await,
],
) )
} else { } else {
TextComponent::translate( TextComponent::translate(
@ -369,7 +366,7 @@ async fn perform(
format!("{}.many.to.one.success", action.get_key()), format!("{}.many.to.one.success", action.get_key()),
[ [
TextComponent::text(advancements.len().to_string()), TextComponent::text(advancements.len().to_string()),
first.get_display_name().await, first.get_display_name(),
], ],
) )
} else { } else {
@ -431,7 +428,7 @@ pub async fn perform_criterion(
.create_without_context_args_slice(&[ .create_without_context_args_slice(&[
TextComponent::text(criterion.to_owned()), TextComponent::text(criterion.to_owned()),
advancement.name(), advancement.name(),
first_player.get_display_name().await, first_player.get_display_name(),
])) ]))
} else { } else {
Err(match action { Err(match action {
@ -451,7 +448,7 @@ pub async fn perform_criterion(
[ [
TextComponent::text(criterion.to_owned()), TextComponent::text(criterion.to_owned()),
advancement.name(), advancement.name(),
first_player.get_display_name().await, first_player.get_display_name(),
], ],
) )
} else { } else {

View File

@ -130,7 +130,7 @@ async fn clear_command_text_output(
([target], true, _) => Err(TextComponent::translate_cross( ([target], true, _) => Err(TextComponent::translate_cross(
translation::java::CLEAR_FAILED_SINGLE, translation::java::CLEAR_FAILED_SINGLE,
translation::bedrock::COMMANDS_CLEAR_FAILURE, translation::bedrock::COMMANDS_CLEAR_FAILURE,
[target.get_display_name().await], [target.get_display_name()],
)), )),
(targets, true, _) => Err(TextComponent::translate_cross( (targets, true, _) => Err(TextComponent::translate_cross(
translation::java::CLEAR_FAILED_MULTIPLE, translation::java::CLEAR_FAILED_MULTIPLE,
@ -142,7 +142,7 @@ async fn clear_command_text_output(
translation::java::COMMANDS_CLEAR_SUCCESS_SINGLE, translation::java::COMMANDS_CLEAR_SUCCESS_SINGLE,
[ [
TextComponent::text(item_count.to_string()), TextComponent::text(item_count.to_string()),
target.get_display_name().await, target.get_display_name(),
], ],
)), )),
(targets, false, false) => Ok(TextComponent::translate_cross( (targets, false, false) => Ok(TextComponent::translate_cross(
@ -158,7 +158,7 @@ async fn clear_command_text_output(
translation::java::COMMANDS_CLEAR_TEST_SINGLE, translation::java::COMMANDS_CLEAR_TEST_SINGLE,
[ [
TextComponent::text(item_count.to_string()), TextComponent::text(item_count.to_string()),
target.get_display_name().await, target.get_display_name(),
], ],
)), )),
(targets, false, true) => Ok(TextComponent::translate_cross( (targets, false, true) => Ok(TextComponent::translate_cross(

View File

@ -87,7 +87,7 @@ impl CommandExecutor for LocationExecutor {
None, None,
); );
send_damage_result(sender, success, amount, target.get_display_name().await).await send_damage_result(sender, success, amount, target.get_display_name()).await
}) })
} }
} }
@ -127,7 +127,7 @@ impl CommandExecutor for EntityExecutor {
cause.as_ref().map(|e| e.as_ref() as &dyn EntityBase), cause.as_ref().map(|e| e.as_ref() as &dyn EntityBase),
); );
send_damage_result(sender, success, amount, target.get_display_name().await).await send_damage_result(sender, success, amount, target.get_display_name()).await
}) })
} }
} }

View File

@ -32,7 +32,7 @@ impl CommandExecutor for GetEntityDataExecutor {
let Some(Arg::Entity(entity)) = args.get(&ARG_ENTITY) else { let Some(Arg::Entity(entity)) = args.get(&ARG_ENTITY) else {
return Err(InvalidConsumption(Some(ARG_ENTITY.into()))); return Err(InvalidConsumption(Some(ARG_ENTITY.into())));
}; };
display_data(entity.as_ref(), entity.get_display_name().await, sender).await display_data(entity.as_ref(), entity.get_display_name(), sender).await
}) })
} }
} }
@ -222,7 +222,7 @@ async fn display_data(
sender: &CommandSender, sender: &CommandSender,
) -> Result<i32, CommandError> { ) -> Result<i32, CommandError> {
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
entity.write_nbt(&mut nbt).await; entity.write_nbt(&mut nbt);
let tag = NbtTag::Compound(nbt); let tag = NbtTag::Compound(nbt);
let result = get_i32_result(&tag)?; let result = get_i32_result(&tag)?;

View File

@ -124,7 +124,7 @@ impl CommandExecutor for GiveExecutor {
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
"commands.effect.give.success.single", "commands.effect.give.success.single",
"commands.effect.give.success.single", "commands.effect.give.success.single",
[translation_name, targets[0].get_display_name().await], [translation_name, targets[0].get_display_name()],
)) ))
.await; .await;
} else { } else {
@ -183,7 +183,7 @@ impl CommandExecutor for ClearExecutor {
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
"commands.effect.clear.everything.success.single", "commands.effect.clear.everything.success.single",
"commands.effect.clear.everything.success.single", "commands.effect.clear.everything.success.single",
[targets[0].get_display_name().await], [targets[0].get_display_name()],
)) ))
.await; .await;
} else { } else {
@ -228,7 +228,7 @@ impl CommandExecutor for ClearExecutor {
effect.translation_key, effect.translation_key,
[], [],
), ),
targets[0].get_display_name().await, targets[0].get_display_name(),
], ],
)) ))
.await; .await;

View File

@ -76,7 +76,7 @@ impl CommandExecutor for Executor {
translation::bedrock::COMMANDS_ENCHANT_SUCCESS, translation::bedrock::COMMANDS_ENCHANT_SUCCESS,
[ [
enchantment.get_fullname(level), enchantment.get_fullname(level),
targets[0].get_display_name().await, targets[0].get_display_name(),
], ],
); );
sender.send_message(msg).await; sender.send_message(msg).await;
@ -141,7 +141,7 @@ async fn enchant_target(
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
translation::java::COMMANDS_ENCHANT_FAILED_ITEMLESS, translation::java::COMMANDS_ENCHANT_FAILED_ITEMLESS,
translation::bedrock::COMMANDS_ENCHANT_NOITEM, translation::bedrock::COMMANDS_ENCHANT_NOITEM,
[target.get_display_name().await], [target.get_display_name()],
); );
return Err(CommandError::CommandFailed(msg)); return Err(CommandError::CommandFailed(msg));
} }
@ -170,9 +170,7 @@ async fn enchant_target(
let inventory = player.inventory(); let inventory = player.inventory();
inventory.set_held_item(item.clone()); inventory.set_held_item(item.clone());
player player.sync_hand_slot(inventory.get_selected_slot() as usize, item);
.sync_hand_slot(inventory.get_selected_slot() as usize, item)
.await;
Ok(()) Ok(())
} }

View File

@ -39,7 +39,7 @@ fn execute_as_modifier<'a>(
let mut sources = Vec::new(); let mut sources = Vec::new();
for target in targets { for target in targets {
let mut source = context.source.as_ref().clone(); let mut source = context.source.as_ref().clone();
let display_name = target.get_display_name().await; let display_name = target.get_display_name();
let name = target.get_name().get_text(); let name = target.get_name().get_text();
source.entity = Some(target.clone()); source.entity = Some(target.clone());
source.name = name; source.name = name;

View File

@ -62,7 +62,7 @@ impl Executor {
translation_key, translation_key,
translation_key, translation_key,
[ [
target.get_display_name().await, target.get_display_name(),
TextComponent::text(val.to_string()), TextComponent::text(val.to_string()),
], ],
)) ))
@ -126,20 +126,20 @@ impl Executor {
match self.exp_type { match self.exp_type {
ExpType::Levels => { ExpType::Levels => {
if self.mode == Mode::Add { if self.mode == Mode::Add {
target.add_experience_levels(amount).await; target.add_experience_levels(amount);
} else { } else {
target.set_experience_level(amount, true).await; target.set_experience_level(amount, true);
} }
} }
ExpType::Points => { ExpType::Points => {
if self.mode == Mode::Add { if self.mode == Mode::Add {
target.add_experience_points(amount).await; target.add_experience_points(amount);
} else { } else {
let current_lvl = target.experience_level.load(Ordering::Relaxed); let current_lvl = target.experience_level.load(Ordering::Relaxed);
if amount > experience::points_in_level(current_lvl) { if amount > experience::points_in_level(current_lvl) {
return false; return false;
} }
target.set_experience_points(amount).await; target.set_experience_points(amount);
} }
} }
} }
@ -195,7 +195,7 @@ impl CommandExecutor for Executor {
} }
// Safe to access first() because successes > 0 // Safe to access first() because successes > 0
let first_name = targets[0].get_display_name().await; let first_name = targets[0].get_display_name();
let msg = self.get_success_message(amount, targets, first_name); let msg = self.get_success_message(amount, targets, first_name);
sender.send_message(msg).await; sender.send_message(msg).await;

View File

@ -353,12 +353,12 @@ impl CommandExecutor for PrintForEntityExecutor {
&context.source, &context.source,
&player.gameprofile, &player.gameprofile,
translation::java::COMMANDS_FETCHPROFILE_ENTITY_SUCCESS, translation::java::COMMANDS_FETCHPROFILE_ENTITY_SUCCESS,
player.get_display_name().await, player.get_display_name(),
) )
.await; .await;
Ok(1) Ok(1)
} else { } else {
Err(NO_PROFILE_ERROR_TYPE.create_without_context(entity.get_display_name().await)) Err(NO_PROFILE_ERROR_TYPE.create_without_context(entity.get_display_name()))
} }
}) })
} }

View File

@ -82,7 +82,7 @@ impl CommandExecutor for TargetExecutor {
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
translation::java::COMMANDS_GAMEMODE_SUCCESS_OTHER, translation::java::COMMANDS_GAMEMODE_SUCCESS_OTHER,
translation::bedrock::COMMANDS_GAMEMODE_SUCCESS_OTHER, translation::bedrock::COMMANDS_GAMEMODE_SUCCESS_OTHER,
[target.get_display_name().await, gamemode_comp], [target.get_display_name(), gamemode_comp],
)) ))
.await; .await;
} }

View File

@ -73,7 +73,7 @@ impl CommandExecutor for Executor {
id: item_name.to_string().into(), id: item_name.to_string().into(),
count: Some(item_count.min(99)), count: Some(item_count.min(99)),
}), }),
targets[0].get_display_name().await, targets[0].get_display_name(),
], ],
) )
} else { } else {

View File

@ -259,7 +259,7 @@ impl CommandExecutor for EntityReplaceExecutor {
translation::java::COMMANDS_ITEM_ENTITY_SET_SUCCESS_SINGLE, translation::java::COMMANDS_ITEM_ENTITY_SET_SUCCESS_SINGLE,
translation::java::COMMANDS_ITEM_ENTITY_SET_SUCCESS_SINGLE, translation::java::COMMANDS_ITEM_ENTITY_SET_SUCCESS_SINGLE,
[ [
targets[0].get_display_name().await, targets[0].get_display_name(),
TextComponent::text("[") TextComponent::text("[")
.add_child(item.translated_name()) .add_child(item.translated_name())
.add_child(TextComponent::text("]")) .add_child(TextComponent::text("]"))

View File

@ -51,13 +51,13 @@ impl CommandExecutor for Executor {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_KICK_SUCCESS, translation::java::COMMANDS_KICK_SUCCESS,
translation::bedrock::COMMANDS_KICK_SUCCESS_REASON, translation::bedrock::COMMANDS_KICK_SUCCESS_REASON,
[target.get_display_name().await, reason.clone()], [target.get_display_name(), reason.clone()],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_KICK_SUCCESS, translation::java::COMMANDS_KICK_SUCCESS,
translation::bedrock::COMMANDS_KICK_SUCCESS, translation::bedrock::COMMANDS_KICK_SUCCESS,
[target.get_display_name().await, reason.clone()], [target.get_display_name(), reason.clone()],
) )
}; };

View File

@ -23,14 +23,14 @@ impl CommandExecutor for TargetsExecutor {
let target_count = targets.len(); let target_count = targets.len();
for target in &targets { for target in &targets {
target.kill(target.as_ref()).await; target.kill(target.as_ref());
} }
let msg = if target_count == 1 { let msg = if target_count == 1 {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_KILL_SUCCESS_SINGLE, translation::java::COMMANDS_KILL_SUCCESS_SINGLE,
translation::bedrock::COMMANDS_KILL_SUCCESSFUL, translation::bedrock::COMMANDS_KILL_SUCCESSFUL,
[targets[0].get_display_name().await], [targets[0].get_display_name()],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(
@ -53,7 +53,7 @@ impl CommandExecutor for SelfExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> { fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move { Box::pin(async move {
let target = context.source.entity_or_err()?; let target = context.source.entity_or_err()?;
target.kill(&*target).await; target.kill(&*target);
context context
.source .source
@ -61,7 +61,7 @@ impl CommandExecutor for SelfExecutor {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_KILL_SUCCESS_SINGLE, translation::java::COMMANDS_KILL_SUCCESS_SINGLE,
translation::bedrock::COMMANDS_KILL_SUCCESSFUL, translation::bedrock::COMMANDS_KILL_SUCCESSFUL,
[target.get_display_name().await], [target.get_display_name()],
), ),
true, true,
) )

View File

@ -12,7 +12,7 @@ use crate::{
context::command_context::CommandContext, context::command_context::CommandContext,
node::{CommandExecutor, CommandExecutorResult, dispatcher::CommandDispatcher}, node::{CommandExecutor, CommandExecutorResult, dispatcher::CommandDispatcher},
}, },
entity::{EntityBase, EntityBaseFuture, player::Player}, entity::{EntityBase, player::Player},
}; };
const DESCRIPTION: &str = "Print the list of online players."; const DESCRIPTION: &str = "Print the list of online players.";
@ -33,7 +33,7 @@ impl CommandExecutor for ListCommandExecutor {
let players_len = players.len(); let players_len = players.len();
let list = match self.0 { let list = match self.0 {
ListMode::Names => get_player_names(&players).await, ListMode::Names => get_player_names(&players),
ListMode::Uuids => get_player_names_and_ids(&players), ListMode::Uuids => get_player_names_and_ids(&players),
}; };
@ -75,10 +75,8 @@ impl CommandExecutor for ListCommandExecutor {
} }
} }
async fn get_player_names(players: &[Arc<Player>]) -> TextComponent { fn get_player_names(players: &[Arc<Player>]) -> TextComponent {
let display_name_futures: Vec<EntityBaseFuture<'_, TextComponent>> = let display_names = players.iter().map(|p| p.get_display_name()).collect();
players.iter().map(|p| p.get_display_name()).collect();
let display_names = futures::future::join_all(display_name_futures).await;
TextComponent::join_with_comma(display_names) TextComponent::join_with_comma(display_names)
} }

View File

@ -42,8 +42,8 @@ impl CommandExecutor for Executor {
.send_message( .send_message(
&TextComponent::text(msg.clone()), &TextComponent::text(msg.clone()),
MSG_COMMAND_OUTGOING, MSG_COMMAND_OUTGOING,
&player.get_display_name().await, &player.get_display_name(),
Some(&target.get_display_name().await), Some(&target.get_display_name()),
) )
.await; .await;
} }
@ -52,8 +52,8 @@ impl CommandExecutor for Executor {
.send_message( .send_message(
&TextComponent::text(msg.clone()), &TextComponent::text(msg.clone()),
MSG_COMMAND_INCOMING, MSG_COMMAND_INCOMING,
&player.get_display_name().await, &player.get_display_name(),
Some(&target.get_display_name().await), Some(&target.get_display_name()),
) )
.await; .await;
} }

View File

@ -155,7 +155,7 @@ impl CommandExecutor for Executor {
translation::bedrock::COMMANDS_PLAYSOUND_SUCCESS, translation::bedrock::COMMANDS_PLAYSOUND_SUCCESS,
[ [
TextComponent::text(sound_name), TextComponent::text(sound_name),
targets[0].get_display_name().await, targets[0].get_display_name(),
], ],
)) ))
.await; .await;

View File

@ -121,7 +121,7 @@ impl CommandExecutor for RecipeGiveExecutor {
translation::java::COMMANDS_RECIPE_GIVE_SUCCESS_SINGLE, translation::java::COMMANDS_RECIPE_GIVE_SUCCESS_SINGLE,
[ [
TextComponent::text(recipe_count_str), TextComponent::text(recipe_count_str),
targets[0].get_display_name().await, targets[0].get_display_name(),
], ],
); );
context.source.send_feedback(msg, true).await; context.source.send_feedback(msg, true).await;
@ -202,7 +202,7 @@ impl CommandExecutor for RecipeTakeExecutor {
translation::java::COMMANDS_RECIPE_TAKE_SUCCESS_SINGLE, translation::java::COMMANDS_RECIPE_TAKE_SUCCESS_SINGLE,
[ [
TextComponent::text(taken_count_str), TextComponent::text(taken_count_str),
targets[0].get_display_name().await, targets[0].get_display_name(),
], ],
); );
context.source.send_feedback(msg, true).await; context.source.send_feedback(msg, true).await;

View File

@ -39,13 +39,23 @@ static ERROR_GENERIC: CommandErrorType<2> = CommandErrorType::new(
); );
#[allow(clippy::assigning_clones)] #[allow(clippy::assigning_clones)]
async fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool { fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool {
let mut current = possible_vehicle.get_entity().vehicle.lock().await.clone(); let mut current = possible_vehicle
.get_entity()
.vehicle
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
while let Some(vehicle) = current { while let Some(vehicle) = current {
if vehicle.get_entity().entity_id == entity.get_entity().entity_id { if vehicle.get_entity().entity_id == entity.get_entity().entity_id {
return true; return true;
} }
current = vehicle.get_entity().vehicle.lock().await.clone(); current = vehicle
.get_entity()
.vehicle
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
} }
false false
} }
@ -79,17 +89,22 @@ impl CommandExecutor for RideMountExecutor {
continue; continue;
} }
if is_riding_recursive(target.as_ref(), vehicle.as_ref()).await { if is_riding_recursive(target.as_ref(), vehicle.as_ref()) {
last_error = Some(ERROR_LOOP.create_without_context()); last_error = Some(ERROR_LOOP.create_without_context());
continue; continue;
} }
let current_vehicle = target.get_entity().vehicle.lock().await.clone(); let current_vehicle = target
.get_entity()
.vehicle
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
if let Some(ref curr_veh) = current_vehicle { if let Some(ref curr_veh) = current_vehicle {
if curr_veh.get_entity().entity_id == vehicle.get_entity().entity_id { if curr_veh.get_entity().entity_id == vehicle.get_entity().entity_id {
last_error = Some(ERROR_ALREADY_RIDING.create_without_context( last_error = Some(ERROR_ALREADY_RIDING.create_without_context(
target.get_display_name().await, target.get_display_name(),
vehicle.get_display_name().await, vehicle.get_display_name(),
)); ));
continue; continue;
} }
@ -102,17 +117,13 @@ impl CommandExecutor for RideMountExecutor {
vehicle vehicle
.get_entity() .get_entity()
.add_passenger(vehicle.clone(), target.clone()) .add_passenger(vehicle.clone(), target.clone());
.await;
success_count += 1; success_count += 1;
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, translation::java::COMMANDS_RIDE_MOUNT_SUCCESS,
translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, translation::java::COMMANDS_RIDE_MOUNT_SUCCESS,
[ [target.get_display_name(), vehicle.get_display_name()],
target.get_display_name().await,
vehicle.get_display_name().await,
],
); );
context.source.send_feedback(msg, true).await; context.source.send_feedback(msg, true).await;
} }
@ -122,8 +133,8 @@ impl CommandExecutor for RideMountExecutor {
return Err(err); return Err(err);
} }
return Err(ERROR_GENERIC.create_without_context( return Err(ERROR_GENERIC.create_without_context(
targets[0].get_display_name().await, targets[0].get_display_name(),
vehicle.get_display_name().await, vehicle.get_display_name(),
)); ));
} }
@ -143,7 +154,12 @@ impl CommandExecutor for RideDismountExecutor {
let mut last_error = None; let mut last_error = None;
for target in &targets { for target in &targets {
let current_vehicle = target.get_entity().vehicle.lock().await.clone(); let current_vehicle = target
.get_entity()
.vehicle
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
if let Some(vehicle) = current_vehicle { if let Some(vehicle) = current_vehicle {
vehicle vehicle
.get_entity() .get_entity()
@ -154,13 +170,12 @@ impl CommandExecutor for RideDismountExecutor {
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS, translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS,
translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS, translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS,
[target.get_display_name().await], [target.get_display_name()],
); );
context.source.send_feedback(msg, true).await; context.source.send_feedback(msg, true).await;
} else { } else {
last_error = Some( last_error =
ERROR_NOT_RIDING.create_without_context(target.get_display_name().await), Some(ERROR_NOT_RIDING.create_without_context(target.get_display_name()));
);
} }
} }
@ -168,9 +183,7 @@ impl CommandExecutor for RideDismountExecutor {
if let Some(err) = last_error { if let Some(err) = last_error {
return Err(err); return Err(err);
} }
return Err( return Err(ERROR_NOT_RIDING.create_without_context(targets[0].get_display_name()));
ERROR_NOT_RIDING.create_without_context(targets[0].get_display_name().await)
);
} }
Ok(success_count) Ok(success_count)

View File

@ -87,7 +87,7 @@ async fn rotate_entity(
/// Sends success message for the rotate command. /// Sends success message for the rotate command.
async fn send_success_message(sender: &CommandSender, target: &dyn crate::entity::EntityBase) { async fn send_success_message(sender: &CommandSender, target: &dyn crate::entity::EntityBase) {
let target_name = target.get_display_name().await; let target_name = target.get_display_name();
sender sender
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
translation::java::COMMANDS_ROTATE_SUCCESS, translation::java::COMMANDS_ROTATE_SUCCESS,

View File

@ -35,7 +35,7 @@ impl CommandExecutor for StopSpectateExecutor {
}; };
if player.gamemode.load() != GameMode::Spectator { if player.gamemode.load() != GameMode::Spectator {
let display_name = player.get_display_name().await; let display_name = player.get_display_name();
return Err(CommandError::CommandFailed(TextComponent::translate_cross( return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR, translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR, translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
@ -76,7 +76,7 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
}; };
if player.gamemode.load() != GameMode::Spectator { if player.gamemode.load() != GameMode::Spectator {
let display_name = player.get_display_name().await; let display_name = player.get_display_name();
return Err(CommandError::CommandFailed(TextComponent::translate_cross( return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR, translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR, translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
@ -98,7 +98,7 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
let target_world = target_entity.world.load_full(); let target_world = target_entity.world.load_full();
let player_world = player.world(); let player_world = player.world();
if !Arc::ptr_eq(&target_world, &player_world) { if !Arc::ptr_eq(&target_world, &player_world) {
let target_name = target.get_display_name().await; let target_name = target.get_display_name();
return Err(CommandError::CommandFailed(TextComponent::translate_cross( return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE, translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE, translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
@ -120,7 +120,7 @@ impl CommandExecutor for SpectateTargetSelfExecutor {
.teleport(pos, Some(yaw), Some(pitch), player_world) .teleport(pos, Some(yaw), Some(pitch), player_world)
.await; .await;
let target_name = target.get_display_name().await; let target_name = target.get_display_name();
sender sender
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED, translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,
@ -155,7 +155,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
// First validate all players // First validate all players
for player in players { for player in players {
if player.gamemode.load() != GameMode::Spectator { if player.gamemode.load() != GameMode::Spectator {
let display_name = player.get_display_name().await; let display_name = player.get_display_name();
return Err(CommandError::CommandFailed(TextComponent::translate_cross( return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR, translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR, translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
@ -173,7 +173,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
let player_world = player.world(); let player_world = player.world();
if !Arc::ptr_eq(&target_world, &player_world) { if !Arc::ptr_eq(&target_world, &player_world) {
let target_name = target.get_display_name().await; let target_name = target.get_display_name();
return Err(CommandError::CommandFailed(TextComponent::translate_cross( return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE, translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE, translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
@ -201,7 +201,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
succeeded += 1; succeeded += 1;
} }
let target_name = target.get_display_name().await; let target_name = target.get_display_name();
sender sender
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED, translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,

View File

@ -66,7 +66,7 @@ impl CommandExecutor for Executor {
} }
}; };
let entity = from_type(entity_type, pos, &world, Uuid::new_v4()); let entity = from_type(entity_type, pos, &world, Uuid::new_v4());
let name = entity.get_display_name().await; let name = entity.get_display_name();
world.spawn_entity(entity); world.spawn_entity(entity);
sender sender
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(

View File

@ -48,8 +48,8 @@ impl CommandExecutor for ChangeExecutor {
for target in &targets { for target in &targets {
let entity = target.get_entity(); let entity = target.get_entity();
let success = match self.0 { let success = match self.0 {
Action::Add => entity.add_scoreboard_tag(&tag).await, Action::Add => entity.add_scoreboard_tag(&tag),
Action::Remove => entity.remove_scoreboard_tag(&tag).await, Action::Remove => entity.remove_scoreboard_tag(&tag),
}; };
if success { if success {
changed += 1; changed += 1;
@ -90,10 +90,7 @@ impl CommandExecutor for ChangeExecutor {
TextComponent::translate_cross( TextComponent::translate_cross(
single_key.0, single_key.0,
single_key.1, single_key.1,
[ [TextComponent::text(tag), targets[0].get_display_name()],
TextComponent::text(tag),
targets[0].get_display_name().await,
],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(
@ -123,7 +120,11 @@ impl CommandExecutor for ListExecutor {
// BTreeSet keeps the output deterministic. // BTreeSet keeps the output deterministic.
let mut all_tags = BTreeSet::new(); let mut all_tags = BTreeSet::new();
for target in &targets { for target in &targets {
let tags = target.get_entity().scoreboard_tags.lock().await; let tags = target
.get_entity()
.scoreboard_tags
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
all_tags.extend(tags.iter().cloned()); all_tags.extend(tags.iter().cloned());
} }
@ -131,7 +132,7 @@ impl CommandExecutor for ListExecutor {
TextComponent::text(all_tags.iter().cloned().collect::<Vec<String>>().join(", ")); TextComponent::text(all_tags.iter().cloned().collect::<Vec<String>>().join(", "));
let msg = if targets.len() == 1 { let msg = if targets.len() == 1 {
let name = targets[0].get_display_name().await; let name = targets[0].get_display_name();
if all_tags.is_empty() { if all_tags.is_empty() {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_TAG_LIST_SINGLE_EMPTY, translation::java::COMMANDS_TAG_LIST_SINGLE_EMPTY,

View File

@ -70,7 +70,7 @@ async fn success_key_and_arg(
multiple_key: &'static str, multiple_key: &'static str,
) -> (&'static str, TextComponent) { ) -> (&'static str, TextComponent) {
if targets.len() == 1 { if targets.len() == 1 {
(single_key, targets[0].get_display_name().await) (single_key, targets[0].get_display_name())
} else { } else {
(multiple_key, TextComponent::text(targets.len().to_string())) (multiple_key, TextComponent::text(targets.len().to_string()))
} }
@ -118,7 +118,7 @@ impl CommandExecutor for EntitiesToEntityExecutor {
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
key, key,
translation::bedrock::COMMANDS_TP_SUCCESSVICTIM, translation::bedrock::COMMANDS_TP_SUCCESSVICTIM,
[target_arg, destination.get_display_name().await], [target_arg, destination.get_display_name()],
)) ))
.await; .await;
@ -383,10 +383,7 @@ impl CommandExecutor for SelfToEntityExecutor {
.send_message(TextComponent::translate_cross( .send_message(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE, translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
translation::bedrock::COMMANDS_TP_SUCCESSVICTIM, translation::bedrock::COMMANDS_TP_SUCCESSVICTIM,
[ [player.get_display_name(), destination.get_display_name()],
player.get_display_name().await,
destination.get_display_name().await,
],
)) ))
.await; .await;
@ -433,7 +430,7 @@ impl CommandExecutor for SelfToPosExecutor {
translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE, translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE,
translation::bedrock::COMMANDS_TP_SUCCESS_COORDINATES, translation::bedrock::COMMANDS_TP_SUCCESS_COORDINATES,
[ [
player.get_display_name().await, player.get_display_name(),
TextComponent::text(pos.x.to_string()), TextComponent::text(pos.x.to_string()),
TextComponent::text(pos.y.to_string()), TextComponent::text(pos.y.to_string()),
TextComponent::text(pos.z.to_string()), TextComponent::text(pos.z.to_string()),

View File

@ -53,11 +53,7 @@ impl CommandExecutor for ClearOrResetExecutor {
} else { } else {
"commands.title.cleared.single" "commands.title.cleared.single"
}; };
TextComponent::translate_cross( TextComponent::translate_cross(text, text, [targets[0].get_display_name()])
text,
text,
[targets[0].get_display_name().await],
)
} else { } else {
let text = if reset { let text = if reset {
"commands.title.reset.multiple" "commands.title.reset.multiple"
@ -105,7 +101,7 @@ impl CommandExecutor for TitleExecutor {
TextComponent::translate_cross( TextComponent::translate_cross(
format!("commands.title.show.{mode_name}.single").clone(), format!("commands.title.show.{mode_name}.single").clone(),
format!("commands.title.show.{mode_name}.single"), format!("commands.title.show.{mode_name}.single"),
[targets[0].get_display_name().await], [targets[0].get_display_name()],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(
@ -148,7 +144,7 @@ impl CommandExecutor for TimesTitleExecutor {
TextComponent::translate_cross( TextComponent::translate_cross(
"commands.title.times.single", "commands.title.times.single",
"commands.title.times.single", "commands.title.times.single",
[targets[0].get_display_name().await], [targets[0].get_display_name()],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(

View File

@ -128,7 +128,7 @@ impl CommandExecutor for TargetPlayerExecutor {
"commands.transfer.success.single", "commands.transfer.success.single",
"commands.transfer.success.single", "commands.transfer.success.single",
[ [
players[0].get_display_name().await, players[0].get_display_name(),
TextComponent::text(hostname.to_owned()), TextComponent::text(hostname.to_owned()),
TextComponent::text(port.to_string()), TextComponent::text(port.to_string()),
], ],

View File

@ -52,9 +52,13 @@ impl CommandExecutor for GetExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let border = world.worldborder.lock().await; let diameter = world
.worldborder
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.new_diameter
.round() as i32;
let diameter = border.new_diameter.round() as i32;
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_GET, translation::java::COMMANDS_WORLDBORDER_GET,
@ -81,20 +85,28 @@ impl CommandExecutor for SetExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let distance = distance_consumer().find_arg_default_name(args)??; let distance = distance_consumer().find_arg_default_name(args)??;
if (distance - border.new_diameter).abs() < f64::EPSILON { let (d, diff) = {
return Err(CommandError::CommandFailed( let mut border = world
pumpkin_macros::translate_cross!( .worldborder
translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE, .lock()
translation::bedrock::COMMANDS_WORLDBORDER_SET_SUCCESS .unwrap_or_else(std::sync::PoisonError::into_inner);
),
)); if (distance - border.new_diameter).abs() < f64::EPSILON {
} return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE,
translation::bedrock::COMMANDS_WORLDBORDER_SET_SUCCESS
),
));
}
let d = border.new_diameter;
border.set_diameter(world, distance, None);
(d, (distance - d) as i32)
};
let d = border.new_diameter;
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_IMMEDIATE, translation::java::COMMANDS_WORLDBORDER_SET_IMMEDIATE,
@ -104,9 +116,7 @@ impl CommandExecutor for SetExecutor {
)) ))
.await; .await;
border.set_diameter(world, distance, None); Ok(diff)
Ok((distance - d) as i32)
}) })
} }
} }
@ -124,14 +134,18 @@ impl CommandExecutor for SetTimeExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let distance = distance_consumer().find_arg_default_name(args)??; let distance = distance_consumer().find_arg_default_name(args)??;
let time = time_consumer().find_arg_default_name(args)??; let time = time_consumer().find_arg_default_name(args)??;
let old_dist = format!("{:.1}", border.new_diameter); let (old_dist, ordering, diff) = {
match distance.total_cmp(&border.new_diameter) { let mut border = world
std::cmp::Ordering::Equal => { .worldborder
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let old_dist = format!("{:.1}", border.new_diameter);
let ordering = distance.total_cmp(&border.new_diameter);
if ordering == std::cmp::Ordering::Equal {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!( pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE, translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE,
@ -139,8 +153,14 @@ impl CommandExecutor for SetTimeExecutor {
), ),
)); ));
} }
let d = border.new_diameter;
border.set_diameter(world, distance, Some(i64::from(time) * 1000));
(old_dist, ordering, (distance - d) as i32)
};
let dist = format!("{distance:.1}");
match ordering {
std::cmp::Ordering::Less => { std::cmp::Ordering::Less => {
let dist = format!("{distance:.1}");
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_SHRINK, translation::java::COMMANDS_WORLDBORDER_SET_SHRINK,
@ -152,7 +172,6 @@ impl CommandExecutor for SetTimeExecutor {
.await; .await;
} }
std::cmp::Ordering::Greater => { std::cmp::Ordering::Greater => {
let dist = format!("{distance:.1}");
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_GROW, translation::java::COMMANDS_WORLDBORDER_SET_GROW,
@ -163,12 +182,10 @@ impl CommandExecutor for SetTimeExecutor {
)) ))
.await; .await;
} }
std::cmp::Ordering::Equal => unreachable!(),
} }
let d = border.new_diameter; Ok(diff)
border.set_diameter(world, distance, Some(i64::from(time) * 1000));
Ok((distance - d) as i32)
}) })
} }
} }
@ -186,8 +203,6 @@ impl CommandExecutor for AddExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let distance_add = distance_consumer().find_arg_default_name(args)??; let distance_add = distance_consumer().find_arg_default_name(args)??;
if distance_add == 0.0 { if distance_add == 0.0 {
@ -199,10 +214,19 @@ impl CommandExecutor for AddExecutor {
)); ));
} }
let distance = border.new_diameter + distance_add; let (dist, old_dist) = {
let mut border = world
.worldborder
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let distance = border.new_diameter + distance_add;
let dist = format!("{distance:.1}");
let old_dist = format!("{:.1}", border.new_diameter);
border.set_diameter(world, distance, None);
(dist, old_dist)
};
let dist = format!("{distance:.1}");
let old_dist = format!("{:.1}", border.new_diameter);
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_IMMEDIATE, translation::java::COMMANDS_WORLDBORDER_SET_IMMEDIATE,
@ -211,7 +235,6 @@ impl CommandExecutor for AddExecutor {
TextComponent::text(old_dist) TextComponent::text(old_dist)
)) ))
.await; .await;
border.set_diameter(world, distance, None);
Ok(distance_add as i32) Ok(distance_add as i32)
}) })
} }
@ -230,16 +253,19 @@ impl CommandExecutor for AddTimeExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let distance_add = distance_consumer().find_arg_default_name(args)??; let distance_add = distance_consumer().find_arg_default_name(args)??;
let time = time_consumer().find_arg_default_name(args)??; let time = time_consumer().find_arg_default_name(args)??;
let distance = distance_add + border.new_diameter; let (distance, old_dist, ordering) = {
let mut border = world
.worldborder
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let old_dist = format!("{:.1}", border.new_diameter); let distance = distance_add + border.new_diameter;
match distance.total_cmp(&border.new_diameter) { let old_dist = format!("{:.1}", border.new_diameter);
std::cmp::Ordering::Equal => { let ordering = distance.total_cmp(&border.new_diameter);
if ordering == std::cmp::Ordering::Equal {
return Err(CommandError::CommandFailed( return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!( pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE, translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE,
@ -247,8 +273,13 @@ impl CommandExecutor for AddTimeExecutor {
), ),
)); ));
} }
border.set_diameter(world, distance, Some(i64::from(time) * 1000));
(distance, old_dist, ordering)
};
let dist = format!("{distance:.1}");
match ordering {
std::cmp::Ordering::Less => { std::cmp::Ordering::Less => {
let dist = format!("{distance:.1}");
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_SHRINK, translation::java::COMMANDS_WORLDBORDER_SET_SHRINK,
@ -260,7 +291,6 @@ impl CommandExecutor for AddTimeExecutor {
.await; .await;
} }
std::cmp::Ordering::Greater => { std::cmp::Ordering::Greater => {
let dist = format!("{distance:.1}");
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_SET_GROW, translation::java::COMMANDS_WORLDBORDER_SET_GROW,
@ -271,10 +301,9 @@ impl CommandExecutor for AddTimeExecutor {
)) ))
.await; .await;
} }
std::cmp::Ordering::Equal => unreachable!(),
} }
border.set_diameter(world, distance, Some(i64::from(time) * 1000));
Ok(distance_add as i32) Ok(distance_add as i32)
}) })
} }
@ -293,10 +322,14 @@ impl CommandExecutor for CenterExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let Vector2 { x, y } = Position2DArgumentConsumer.find_arg_default_name(args)?; let Vector2 { x, y } = Position2DArgumentConsumer.find_arg_default_name(args)?;
world
.worldborder
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_center(world, x, y);
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_CENTER_SUCCESS, translation::java::COMMANDS_WORLDBORDER_CENTER_SUCCESS,
@ -305,7 +338,7 @@ impl CommandExecutor for CenterExecutor {
TextComponent::text(format!("{y:.2}")) TextComponent::text(format!("{y:.2}"))
)) ))
.await; .await;
border.set_center(world, x, y);
Ok(0) Ok(0)
}) })
} }
@ -324,21 +357,29 @@ impl CommandExecutor for DamageAmountExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let damage_per_block = damage_per_block_consumer().find_arg_default_name(args)??; let damage_per_block = damage_per_block_consumer().find_arg_default_name(args)??;
if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON { let old_damage = {
return Err(CommandError::CommandFailed( let mut border = world
pumpkin_macros::translate_cross!( .worldborder
translation::java::COMMANDS_WORLDBORDER_DAMAGE_AMOUNT_FAILED, .lock()
translation::bedrock::COMMANDS_WORLDBORDER_DAMAGE_AMOUNT_SUCCESS .unwrap_or_else(std::sync::PoisonError::into_inner);
),
)); if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON {
} return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_DAMAGE_AMOUNT_FAILED,
translation::bedrock::COMMANDS_WORLDBORDER_DAMAGE_AMOUNT_SUCCESS
),
));
}
let old_damage = format!("{:.2}", border.damage_per_block);
border.damage_per_block = damage_per_block;
old_damage
};
let damage = format!("{damage_per_block:.2}"); let damage = format!("{damage_per_block:.2}");
let old_damage = format!("{:.2}", border.damage_per_block);
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_DAMAGE_AMOUNT_SUCCESS, translation::java::COMMANDS_WORLDBORDER_DAMAGE_AMOUNT_SUCCESS,
@ -347,7 +388,7 @@ impl CommandExecutor for DamageAmountExecutor {
TextComponent::text(old_damage) TextComponent::text(old_damage)
)) ))
.await; .await;
border.damage_per_block = damage_per_block;
Ok(damage_per_block as i32) Ok(damage_per_block as i32)
}) })
} }
@ -366,21 +407,29 @@ impl CommandExecutor for DamageBufferExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let buffer = damage_buffer_consumer().find_arg_default_name(args)??; let buffer = damage_buffer_consumer().find_arg_default_name(args)??;
if (buffer - border.buffer).abs() < f32::EPSILON { let old_buf = {
return Err(CommandError::CommandFailed( let mut border = world
pumpkin_macros::translate_cross!( .worldborder
translation::java::COMMANDS_WORLDBORDER_DAMAGE_BUFFER_FAILED, .lock()
translation::bedrock::COMMANDS_WORLDBORDER_DAMAGE_BUFFER_SUCCESS .unwrap_or_else(std::sync::PoisonError::into_inner);
),
)); if (buffer - border.buffer).abs() < f32::EPSILON {
} return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_DAMAGE_BUFFER_FAILED,
translation::bedrock::COMMANDS_WORLDBORDER_DAMAGE_BUFFER_SUCCESS
),
));
}
let old_buf = format!("{:.2}", border.buffer);
border.buffer = buffer;
old_buf
};
let buf = format!("{buffer:.2}"); let buf = format!("{buffer:.2}");
let old_buf = format!("{:.2}", border.buffer);
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_DAMAGE_BUFFER_SUCCESS, translation::java::COMMANDS_WORLDBORDER_DAMAGE_BUFFER_SUCCESS,
@ -389,7 +438,7 @@ impl CommandExecutor for DamageBufferExecutor {
TextComponent::text(old_buf) TextComponent::text(old_buf)
)) ))
.await; .await;
border.buffer = buffer;
Ok(buffer as i32) Ok(buffer as i32)
}) })
} }
@ -408,28 +457,37 @@ impl CommandExecutor for WarningDistanceExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let distance = warning_distance_consumer().find_arg_default_name(args)??; let distance = warning_distance_consumer().find_arg_default_name(args)??;
if distance == border.warning_blocks { let old_warning = {
return Err(CommandError::CommandFailed( let mut border = world
pumpkin_macros::translate_cross!( .worldborder
translation::java::COMMANDS_WORLDBORDER_WARNING_DISTANCE_FAILED, .lock()
translation::bedrock::COMMANDS_WORLDBORDER_WARNING_DISTANCE_SUCCESS .unwrap_or_else(std::sync::PoisonError::into_inner);
),
)); if distance == border.warning_blocks {
} return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_WARNING_DISTANCE_FAILED,
translation::bedrock::COMMANDS_WORLDBORDER_WARNING_DISTANCE_SUCCESS
),
));
}
let old_warning = border.warning_blocks;
border.set_warning_distance(world, distance);
old_warning
};
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_WARNING_DISTANCE_SUCCESS, translation::java::COMMANDS_WORLDBORDER_WARNING_DISTANCE_SUCCESS,
translation::bedrock::COMMANDS_WORLDBORDER_WARNING_DISTANCE_SUCCESS, translation::bedrock::COMMANDS_WORLDBORDER_WARNING_DISTANCE_SUCCESS,
TextComponent::text(distance.to_string()), TextComponent::text(distance.to_string()),
TextComponent::text(border.warning_blocks.to_string()) TextComponent::text(old_warning.to_string())
)) ))
.await; .await;
border.set_warning_distance(world, distance);
Ok(distance) Ok(distance)
}) })
} }
@ -448,28 +506,37 @@ impl CommandExecutor for WarningTimeExecutor {
// TODO: Maybe ask player for world, or get the current world // TODO: Maybe ask player for world, or get the current world
let worlds = server.worlds.load(); let worlds = server.worlds.load();
let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let world = worlds.first().ok_or(CommandError::InvalidRequirement)?;
let mut border = world.worldborder.lock().await;
let time = time_consumer().find_arg_default_name(args)??; let time = time_consumer().find_arg_default_name(args)??;
if time == border.warning_time { let old_time = {
return Err(CommandError::CommandFailed( let mut border = world
pumpkin_macros::translate_cross!( .worldborder
translation::java::COMMANDS_WORLDBORDER_WARNING_TIME_FAILED, .lock()
translation::bedrock::COMMANDS_WORLDBORDER_WARNING_TIME_SUCCESS .unwrap_or_else(std::sync::PoisonError::into_inner);
),
)); if time == border.warning_time {
} return Err(CommandError::CommandFailed(
pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_WARNING_TIME_FAILED,
translation::bedrock::COMMANDS_WORLDBORDER_WARNING_TIME_SUCCESS
),
));
}
let old_time = border.warning_time;
border.set_warning_delay(world, time);
old_time
};
sender sender
.send_message(pumpkin_macros::translate_cross!( .send_message(pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WORLDBORDER_WARNING_TIME_SUCCESS, translation::java::COMMANDS_WORLDBORDER_WARNING_TIME_SUCCESS,
translation::bedrock::COMMANDS_WORLDBORDER_WARNING_TIME_SUCCESS, translation::bedrock::COMMANDS_WORLDBORDER_WARNING_TIME_SUCCESS,
TextComponent::text(time.to_string()), TextComponent::text(time.to_string()),
TextComponent::text(border.warning_time.to_string()) TextComponent::text(old_time.to_string())
)) ))
.await; .await;
border.set_warning_delay(world, time);
Ok(time) Ok(time)
}) })
} }

View File

@ -203,7 +203,7 @@ impl CommandSource {
#[must_use] #[must_use]
pub async fn with_entity(self, entity: Arc<dyn EntityBase>) -> Self { pub async fn with_entity(self, entity: Arc<dyn EntityBase>) -> Self {
let name = entity.get_name().get_text(); let name = entity.get_name().get_text();
let display_name = entity.get_display_name().await; let display_name = entity.get_display_name();
Self { Self {
output: self.output, output: self.output,
world: self.world, world: self.world,

View File

@ -331,8 +331,8 @@ impl CommandSender {
Some(player.clone()), Some(player.clone()),
player.position(), player.position(),
player.rotation().into(), player.rotation().into(),
player.get_display_name().await.get_text(), player.get_display_name().get_text(),
player.get_display_name().await, player.get_display_name(),
server.clone(), server.clone(),
), ),
Self::CommandBlock(command_entity, world) => { Self::CommandBlock(command_entity, world) => {

View File

@ -54,7 +54,7 @@ impl ServerPlayerData {
player.on_handled_screen_closed().await; player.on_handled_screen_closed().await;
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt).await; player.write_nbt(&mut nbt);
let storage = self.storage.clone(); let storage = self.storage.clone();
let uuid = player.gameprofile.id; let uuid = player.gameprofile.id;
@ -84,7 +84,7 @@ impl ServerPlayerData {
for world in server.worlds.load().iter() { for world in server.worlds.load().iter() {
for player in world.players.load().iter() { for player in world.players.load().iter() {
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt).await; player.write_nbt(&mut nbt);
let storage = self.storage.clone(); let storage = self.storage.clone();
let uuid = player.gameprofile.id; let uuid = player.gameprofile.id;
@ -194,7 +194,7 @@ impl ServerPlayerData {
let uuid = player.gameprofile.id; let uuid = player.gameprofile.id;
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt).await; player.write_nbt(&mut nbt);
let storage = self.storage.clone(); let storage = self.storage.clone();
tokio::task::spawn_blocking(move || storage.save_player_data(&uuid, nbt)) tokio::task::spawn_blocking(move || storage.save_player_data(&uuid, nbt))

View File

@ -4,9 +4,8 @@ use pumpkin_data::{Block, BlockStateId};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::{chunk::ChunkHeightmapType, world::BlockFlags}; use pumpkin_world::{chunk::ChunkHeightmapType, world::BlockFlags};
use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tokio::sync::Mutex; use std::sync::{Arc, Mutex};
use crate::entity::{ use crate::entity::{
Entity, EntityBase, Entity, EntityBase,
@ -284,31 +283,43 @@ impl EnderDragonEntity {
} }
} }
pub async fn set_phase(&self, phase_type: EnderDragonPhase) { pub fn set_phase(&self, phase_type: EnderDragonPhase) {
let mut phase_lock = self.phase.lock().await; let mut phase_lock = self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if *phase_lock == phase_type { if *phase_lock == phase_type {
return; return;
} }
let old_phase = self.phase_manager.get_phase(*phase_lock); let old_phase = self.phase_manager.get_phase(*phase_lock);
old_phase.end(self).await; old_phase.end(self);
*phase_lock = phase_type; *phase_lock = phase_type;
let new_phase = self.phase_manager.get_phase(phase_type); let new_phase = self.phase_manager.get_phase(phase_type);
new_phase.begin(self).await; new_phase.begin(self);
} }
async fn ensure_nodes_initialized(&self) { fn ensure_nodes_initialized(&self) {
let mut initialized = self.nodes_initialized.lock().await; let mut initialized = self
.nodes_initialized
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if *initialized { if *initialized {
return; return;
} }
let world = self.mob_entity.living_entity.entity.world.load(); let world = self.mob_entity.living_entity.entity.world.load();
let fight_origin = self.fight_origin.lock().await; let fight_origin = self
.fight_origin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut nodes = self.nodes.lock().await; let mut nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for i in 0..NODE_COUNT { for i in 0..NODE_COUNT {
let mut y_adjustment = 5; let mut y_adjustment = 5;
let node_x; let node_x;
@ -346,19 +357,28 @@ impl EnderDragonEntity {
let new_path = find_path(&nodes, nearest, dest, None); let new_path = find_path(&nodes, nearest, dest, None);
drop(nodes); drop(nodes);
*self.target_node.lock().await = dest; *self
*self.path.lock().await = new_path; .target_node
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = dest;
*self
.path
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = new_path;
*initialized = true; *initialized = true;
} }
pub async fn find_closest_node(&self) -> usize { pub fn find_closest_node(&self) -> usize {
let pos = self.mob_entity.living_entity.entity.pos.load(); let pos = self.mob_entity.living_entity.entity.pos.load();
self.find_closest_node_to(pos).await self.find_closest_node_to(pos)
} }
pub async fn find_closest_node_to(&self, pos: Vector3<f64>) -> usize { pub fn find_closest_node_to(&self, pos: Vector3<f64>) -> usize {
self.ensure_nodes_initialized().await; self.ensure_nodes_initialized();
let nodes = self.nodes.lock().await; let nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Self::nearest_node_in(&nodes, pos) Self::nearest_node_in(&nodes, pos)
} }
@ -408,7 +428,7 @@ impl EnderDragonEntity {
Vector3::new(vec.x * cos - vec.z * sin, vec.y, vec.z * cos + vec.x * sin) Vector3::new(vec.x * cos - vec.z * sin, vec.y, vec.z * cos + vec.x * sin)
} }
pub async fn steer_toward( pub fn steer_toward(
&self, &self,
pos: Vector3<f64>, pos: Vector3<f64>,
target: Vector3<f64>, target: Vector3<f64>,
@ -459,7 +479,10 @@ impl EnderDragonEntity {
} }
y_rot_d = y_rot_d.clamp(-50.0, 50.0); y_rot_d = y_rot_d.clamp(-50.0, 50.0);
let mut y_rot_a = self.yaw_rot_accel.lock().await; let mut y_rot_a = self
.yaw_rot_accel
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*y_rot_a *= 0.8; *y_rot_a *= 0.8;
*y_rot_a += y_rot_d * turn_speed; *y_rot_a += y_rot_d * turn_speed;
entity.yaw.store(yaw + *y_rot_a * 0.1); entity.yaw.store(yaw + *y_rot_a * 0.1);
@ -485,11 +508,24 @@ impl EnderDragonEntity {
)); ));
} }
async fn update_flap_time(&self) { fn update_flap_time(&self) {
let sitting = self.phase.lock().await.is_sitting(); let sitting = self
let in_wall = *self.in_wall.lock().await; .phase
let mut flap = self.flap_time.lock().await; .lock()
let mut o_flap = self.o_flap_time.lock().await; .unwrap_or_else(std::sync::PoisonError::into_inner)
.is_sitting();
let in_wall = *self
.in_wall
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut flap = self
.flap_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut o_flap = self
.o_flap_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*o_flap = *flap; *o_flap = *flap;
*flap += if sitting { *flap += if sitting {
@ -504,11 +540,19 @@ impl EnderDragonEntity {
} }
} }
async fn tick_growl(&self) { fn tick_growl(&self) {
if self.phase.lock().await.is_sitting() { if self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_sitting()
{
return; return;
} }
let mut t = self.growl_time.lock().await; let mut t = self
.growl_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if *t > 0 { if *t > 0 {
*t -= 1; *t -= 1;
} else { } else {
@ -537,7 +581,7 @@ impl EnderDragonEntity {
.cloned() .cloned()
} }
async fn handle_player_collisions(&self) { fn handle_player_collisions(&self) {
let world = self.mob_entity.living_entity.entity.world.load(); let world = self.mob_entity.living_entity.entity.world.load();
let dragon_bbox = self.mob_entity.living_entity.entity.bounding_box.load(); let dragon_bbox = self.mob_entity.living_entity.entity.bounding_box.load();
@ -563,7 +607,12 @@ impl EnderDragonEntity {
.apply_knockback(4.0, xd / dd, zd / dd); .apply_knockback(4.0, xd / dd, zd / dd);
player.get_entity().send_velocity(); player.get_entity().send_velocity();
if !self.phase.lock().await.is_sitting() { if !self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_sitting()
{
player.damage(self, 5.0, DamageType::MOB_ATTACK); player.damage(self, 5.0, DamageType::MOB_ATTACK);
} }
} }
@ -596,8 +645,11 @@ impl EnderDragonEntity {
} }
} }
async fn tick_block_breaking(&self) { fn tick_block_breaking(&self) {
let phase_type = *self.phase.lock().await; let phase_type = *self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if phase_type.is_sitting() || phase_type == EnderDragonPhase::Dying { if phase_type.is_sitting() || phase_type == EnderDragonPhase::Dying {
return; return;
} }
@ -622,9 +674,11 @@ impl EnderDragonEntity {
} }
} }
async fn tick_parts(&self) { fn tick_parts(&self) {
let history: tokio::sync::MutexGuard<'_, DragonFlightHistory> = let history = self
self.flight_history.lock().await; .flight_history
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let p5 = history.get(5); let p5 = history.get(5);
let p10 = history.get(10); let p10 = history.get(10);
let p0 = history.get(0); let p0 = history.get(0);
@ -657,13 +711,21 @@ impl EnderDragonEntity {
pos.z - ss1 * 4.5, pos.z - ss1 * 4.5,
)); ));
let head_y_offset = if self.phase.lock().await.is_sitting() { let head_y_offset = if self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_sitting()
{
-1.0 -1.0
} else { } else {
(p5.y - p0.y) as f64 (p5.y - p0.y) as f64
}; };
let yaw_accel = *self.yaw_rot_accel.lock().await; let yaw_accel = *self
.yaw_rot_accel
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let rot2 = (yaw - yaw_accel * 0.01) * (std::f32::consts::PI / 180.0); let rot2 = (yaw - yaw_accel * 0.01) * (std::f32::consts::PI / 180.0);
let ss2 = rot2.sin() as f64; let ss2 = rot2.sin() as f64;
let cc2 = rot2.cos() as f64; let cc2 = rot2.cos() as f64;
@ -701,18 +763,21 @@ impl EnderDragonEntity {
} }
} }
pub async fn ai_step(&self) { pub fn ai_step(&self) {
self.mob_entity.living_entity.entity.update_last_pos(); self.mob_entity.living_entity.entity.update_last_pos();
self.ensure_nodes_initialized().await; self.ensure_nodes_initialized();
self.update_flap_time().await; self.update_flap_time();
{ {
let y = self.mob_entity.living_entity.entity.pos.load().y; let y = self.mob_entity.living_entity.entity.pos.load().y;
let yaw = self.mob_entity.living_entity.entity.yaw.load(); let yaw = self.mob_entity.living_entity.entity.yaw.load();
self.flight_history.lock().await.record(y, yaw); self.flight_history
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.record(y, yaw);
}; };
self.tick_growl().await; self.tick_growl();
self.tick_crystal_healing(); self.tick_crystal_healing();
{ {
@ -725,23 +790,32 @@ impl EnderDragonEntity {
} }
} }
let phase_type: EnderDragonPhase = *self.phase.lock().await; let phase_type: EnderDragonPhase = *self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let phase = self.phase_manager.get_phase(phase_type); let phase = self.phase_manager.get_phase(phase_type);
if phase_type.is_sitting() { if phase_type.is_sitting() {
*self.ticks_sitting.lock().await += 1; *self
.ticks_sitting
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) += 1;
} else { } else {
self.handle_player_collisions().await; self.handle_player_collisions();
self.tick_block_breaking().await; self.tick_block_breaking();
} }
phase.tick(self).await; phase.tick(self);
if phase_type == EnderDragonPhase::Dying { if phase_type == EnderDragonPhase::Dying {
return; return;
} }
let target_location = *self.target_location.lock().await; let target_location = *self
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(target) = target_location { if let Some(target) = target_location {
let pos = self.mob_entity.living_entity.entity.pos.load(); let pos = self.mob_entity.living_entity.entity.pos.load();
self.steer_toward( self.steer_toward(
@ -750,18 +824,23 @@ impl EnderDragonEntity {
phase.get_fly_speed(), phase.get_fly_speed(),
phase.get_turn_speed(), phase.get_turn_speed(),
0.5, 0.5,
) );
.await;
} }
self.mob_entity.living_entity.entity.send_pos_rot(); self.mob_entity.living_entity.entity.send_pos_rot();
self.tick_parts().await; self.tick_parts();
} }
pub async fn hurt(&self, damage: f32) { pub fn hurt(&self, damage: f32) {
let phase_type: EnderDragonPhase = *self.phase.lock().await; let phase_type: EnderDragonPhase = *self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if phase_type.is_sitting() { if phase_type.is_sitting() {
*self.sitting_damage_received.lock().await += damage; *self
.sitting_damage_received
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) += damage;
} }
} }
} }
@ -772,31 +851,13 @@ impl Mob for EnderDragonEntity {
} }
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) { fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
let entity_id = self.mob_entity.living_entity.entity.entity_id; self.ai_step();
let world = self.mob_entity.living_entity.entity.world.load_full();
tokio::spawn(async move {
let Some(entity) = world.get_entity_by_id(entity_id) else {
return;
};
let Some(dragon) = entity.cast_any().downcast_ref::<Self>() else {
return;
};
dragon.ai_step().await;
});
} }
fn on_damage(&self, _damage_type: DamageType, _source: Option<&dyn EntityBase>) { fn on_damage(&self, _damage_type: DamageType, _source: Option<&dyn EntityBase>) {
let living = &self.mob_entity.living_entity; let living = &self.mob_entity.living_entity;
if living.health.load() <= 0.0 { if living.health.load() <= 0.0 {
let world = self.mob_entity.living_entity.entity.world.load_full(); self.set_phase(EnderDragonPhase::Dying);
let entity_id = self.mob_entity.living_entity.entity.entity_id;
tokio::spawn(async move {
if let Some(entity) = world.get_entity_by_id(entity_id)
&& let Some(dragon) = entity.cast_any().downcast_ref::<Self>()
{
dragon.set_phase(EnderDragonPhase::Dying).await;
}
});
} }
} }

View File

@ -1,7 +1,6 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::EntityBase; use crate::entity::EntityBase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y, Vector3Ext}; use crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
pub struct ChargingPhase; pub struct ChargingPhase;
@ -11,34 +10,41 @@ impl super::Phase for ChargingPhase {
EnderDragonPhase::Charging EnderDragonPhase::Charging
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let target_id = {
let target_id = { let guard = dragon
let guard = dragon.target_player.lock().await; .target_player
*guard .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*guard
};
let world = dragon.mob_entity.living_entity.entity.world.load();
let pos = dragon.mob_entity.living_entity.entity.pos.load();
let target_pos = if let Some(id) = target_id
&& let Some(player) = world.players.load().iter().find(|p| p.gameprofile.id == id)
{
player.get_entity().pos.load()
} else {
let origin = {
let guard = dragon
.fight_origin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.0
}; };
let world = dragon.mob_entity.living_entity.entity.world.load(); Vector3::new(origin.x as f64, NODE_Y as f64 - 20.0, origin.z as f64)
let pos = dragon.mob_entity.living_entity.entity.pos.load(); };
let target_pos = if let Some(id) = target_id if pos.distance_squared(target_pos) < 25.0 {
&& let Some(player) = world.players.load().iter().find(|p| p.gameprofile.id == id) dragon.set_phase(EnderDragonPhase::Hovering);
{ return;
player.get_entity().pos.load() }
} else {
let origin = {
let guard = dragon.fight_origin.lock().await;
guard.0
};
Vector3::new(origin.x as f64, NODE_Y as f64 - 20.0, origin.z as f64)
};
if pos.distance_squared(target_pos) < 25.0 { *dragon
dragon.set_phase(EnderDragonPhase::Hovering).await; .target_location
return; .lock()
} .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(target_pos);
*dragon.target_location.lock().await = Some(target_pos);
})
} }
fn get_fly_speed(&self) -> f32 { fn get_fly_speed(&self) -> f32 {

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext, find_path}; use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext, find_path};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -11,84 +10,104 @@ impl super::Phase for CirclingPhase {
EnderDragonPhase::Circling EnderDragonPhase::Circling
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let pos = dragon.mob_entity.living_entity.entity.pos.load();
let pos = dragon.mob_entity.living_entity.entity.pos.load(); let mut target_location = dragon
let mut target_location = dragon.target_location.lock().await; .target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let d0 = target_location.map_or(0.0, |loc| pos.distance_squared(loc)); let d0 = target_location.map_or(0.0, |loc| pos.distance_squared(loc));
if target_location.is_none() if target_location.is_none()
|| !(100.0..=22500.0).contains(&d0) || !(100.0..=22500.0).contains(&d0)
|| dragon || dragon
.mob_entity .mob_entity
.living_entity .living_entity
.entity .entity
.horizontal_collision .horizontal_collision
.load(Ordering::Relaxed) .load(Ordering::Relaxed)
{ {
let mut path = dragon.path.lock().await; let mut path = dragon
if path.is_empty() { .path
drop(path); .lock()
let i = dragon.find_closest_node().await; .unwrap_or_else(std::sync::PoisonError::into_inner);
let mut j = i as i32; if path.is_empty() {
drop(path);
let i = dragon.find_closest_node();
let mut j = i as i32;
let mut clockwise = dragon.holding_pattern_clockwise.lock().await; let mut clockwise = dragon
if rand::random_range(0..8) == 0 { .holding_pattern_clockwise
*clockwise = !*clockwise; .lock()
j = i as i32 + 6; .unwrap_or_else(std::sync::PoisonError::into_inner);
} if rand::random_range(0..8) == 0 {
if *clockwise { *clockwise = !*clockwise;
j += 1; j = i as i32 + 6;
} else {
j -= 1;
}
drop(clockwise);
let world = dragon.mob_entity.living_entity.entity.world.load();
let crystals_alive = world.dragon_fight.as_ref().is_some_and(|fight| {
fight
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.alive_crystals()
> 0
});
let j = if crystals_alive {
j.rem_euclid(12) as usize
} else {
(j - 12).rem_euclid(8) as usize + 12
};
let mut path_lock = dragon.path.lock().await;
let nodes = dragon.nodes.lock().await;
*path_lock = find_path(&nodes, i, j, None);
drop(nodes);
path = path_lock;
} }
if *clockwise {
j += 1;
} else {
j -= 1;
}
drop(clockwise);
if let Some(next_node_idx) = path.first().copied() { let world = dragon.mob_entity.living_entity.entity.world.load();
path.remove(0); let crystals_alive = world.dragon_fight.as_ref().is_some_and(|fight| {
let nodes = dragon.nodes.lock().await; fight
if let Some(node) = nodes[next_node_idx] { .lock()
let mut y_target = node.y + rand::random_range(0.0..20.0); .unwrap_or_else(std::sync::PoisonError::into_inner)
while y_target < node.y { .alive_crystals()
y_target = node.y + rand::random_range(0.0..20.0); > 0
} });
*target_location = Some(Vector3::new(node.x, y_target, node.z));
let j = if crystals_alive {
j.rem_euclid(12) as usize
} else {
(j - 12).rem_euclid(8) as usize + 12
};
let mut path_lock = dragon
.path
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let nodes = dragon
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*path_lock = find_path(&nodes, i, j, None);
drop(nodes);
path = path_lock;
}
if let Some(next_node_idx) = path.first().copied() {
path.remove(0);
let nodes = dragon
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(node) = nodes[next_node_idx] {
let mut y_target = node.y + rand::random_range(0.0..20.0);
while y_target < node.y {
y_target = node.y + rand::random_range(0.0..20.0);
} }
*target_location = Some(Vector3::new(node.x, y_target, node.z));
} }
} }
drop(target_location); }
drop(target_location);
if rand::random_range(0..64) == 0 { if rand::random_range(0..64) == 0 {
if rand::random_bool(0.5) { if rand::random_bool(0.5) {
dragon.set_phase(EnderDragonPhase::FlyToPortal).await; dragon.set_phase(EnderDragonPhase::FlyToPortal);
} else if let Some(player) = dragon.find_nearest_player() { } else if let Some(player) = dragon.find_nearest_player() {
*dragon.target_player.lock().await = Some(player.gameprofile.id); *dragon
dragon.set_phase(EnderDragonPhase::Strafing).await; .target_player
} .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
Some(player.gameprofile.id);
dragon.set_phase(EnderDragonPhase::Strafing);
} }
}) }
} }
} }

View File

@ -1,7 +1,6 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{DEATH_TIMER_MAX, EnderDragonEntity}; use crate::entity::boss::ender_dragon::{DEATH_TIMER_MAX, EnderDragonEntity};
use crate::entity::experience_orb::ExperienceOrbEntity; use crate::entity::experience_orb::ExperienceOrbEntity;
use futures::future::BoxFuture;
use pumpkin_data::particle::Particle; use pumpkin_data::particle::Particle;
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -13,85 +12,79 @@ impl super::Phase for DyingPhase {
EnderDragonPhase::Dying EnderDragonPhase::Dying
} }
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn begin(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { *dragon
*dragon.target_location.lock().await = None; .target_location
}) .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let mut t = dragon
let mut t = dragon.dragon_death_time.lock().await; .dragon_death_time
*t += 1; .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*t += 1;
let entity = &dragon.mob_entity.living_entity.entity; let entity = &dragon.mob_entity.living_entity.entity;
let world = entity.world.load(); let world = entity.world.load();
if *t == 1 { if *t == 1 {
world.play_sound( world.play_sound(
Sound::EntityEnderDragonDeath, Sound::EntityEnderDragonDeath,
SoundCategory::Hostile, SoundCategory::Hostile,
&entity.pos.load(), &entity.pos.load(),
); );
} }
if *t >= 180 && *t <= 200 { if *t >= 180 && *t <= 200 {
let xo = (rand::random::<f32>() - 0.5) * 8.0; let xo = (rand::random::<f32>() - 0.5) * 8.0;
let yo = (rand::random::<f32>() - 0.5) * 4.0; let yo = (rand::random::<f32>() - 0.5) * 4.0;
let zo = (rand::random::<f32>() - 0.5) * 8.0; let zo = (rand::random::<f32>() - 0.5) * 8.0;
let pos = entity.pos.load(); let pos = entity.pos.load();
world.spawn_particle( world.spawn_particle(
Vector3::new( Vector3::new(
pos.x + xo as f64, pos.x + xo as f64,
pos.y + 2.0 + yo as f64, pos.y + 2.0 + yo as f64,
pos.z + zo as f64, pos.z + zo as f64,
), ),
Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0),
0.0, 0.0,
1, 1,
Particle::ExplosionEmitter, Particle::ExplosionEmitter,
); );
} }
let xp_count = if let Some(ref fight_mutex) = world.dragon_fight let xp_count = if let Some(ref fight_mutex) = world.dragon_fight
&& !fight_mutex && !fight_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.has_previously_killed_dragon()
{
12000
} else {
500
};
if *t > 150 && *t % 5 == 0 {
ExperienceOrbEntity::spawn(&world, entity.pos.load(), (xp_count as f32 * 0.08) as u32);
}
entity.velocity.store(Vector3::new(0.0, 0.1, 0.0));
if *t >= DEATH_TIMER_MAX {
ExperienceOrbEntity::spawn(&world, entity.pos.load(), (xp_count as f32 * 0.2) as u32);
if let Some(ref fight_mutex) = world.dragon_fight {
fight_mutex
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) .unwrap_or_else(std::sync::PoisonError::into_inner)
.has_previously_killed_dragon() .set_dragon_killed(&world, entity.entity_uuid);
{
12000
} else {
500
};
if *t > 150 && *t % 5 == 0 {
ExperienceOrbEntity::spawn(
&world,
entity.pos.load(),
(xp_count as f32 * 0.08) as u32,
);
} }
for part in &dragon.parts {
entity.velocity.store(Vector3::new(0.0, 0.1, 0.0)); part.entity.remove();
if *t >= DEATH_TIMER_MAX {
ExperienceOrbEntity::spawn(
&world,
entity.pos.load(),
(xp_count as f32 * 0.2) as u32,
);
if let Some(ref fight_mutex) = world.dragon_fight {
fight_mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_dragon_killed(&world, entity.entity_uuid);
}
for part in &dragon.parts {
part.entity.remove();
}
entity.remove();
} }
}) entity.remove();
}
} }
} }

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext}; use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
pub struct FlyToPortalPhase; pub struct FlyToPortalPhase;
@ -10,21 +9,25 @@ impl super::Phase for FlyToPortalPhase {
EnderDragonPhase::FlyToPortal EnderDragonPhase::FlyToPortal
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let origin = {
let origin = { let guard = dragon
let guard = dragon.fight_origin.lock().await; .fight_origin
guard.0 .lock()
}; .unwrap_or_else(std::sync::PoisonError::into_inner);
let target = Vector3::new(origin.x as f64, origin.y as f64 + 10.0, origin.z as f64); guard.0
let pos = dragon.mob_entity.living_entity.entity.pos.load(); };
let target = Vector3::new(origin.x as f64, origin.y as f64 + 10.0, origin.z as f64);
let pos = dragon.mob_entity.living_entity.entity.pos.load();
if pos.distance_squared(target) < 4.0 { if pos.distance_squared(target) < 4.0 {
dragon.set_phase(EnderDragonPhase::LandingApproach).await; dragon.set_phase(EnderDragonPhase::LandingApproach);
return; return;
} }
*dragon.target_location.lock().await = Some(target); *dragon
}) .target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(target);
} }
} }

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y}; use crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
pub struct HoveringPhase; pub struct HoveringPhase;
@ -10,20 +9,24 @@ impl super::Phase for HoveringPhase {
EnderDragonPhase::Hovering EnderDragonPhase::Hovering
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let origin = {
let origin = { let guard = dragon
let guard = dragon.fight_origin.lock().await; .fight_origin
guard.0 .lock()
}; .unwrap_or_else(std::sync::PoisonError::into_inner);
let target = Vector3::new(origin.x as f64, NODE_Y as f64 + 10.0, origin.z as f64); guard.0
};
let target = Vector3::new(origin.x as f64, NODE_Y as f64 + 10.0, origin.z as f64);
if rand::random_bool(0.01) { if rand::random_bool(0.01) {
dragon.set_phase(EnderDragonPhase::TakingOff).await; dragon.set_phase(EnderDragonPhase::TakingOff);
return; return;
} }
*dragon.target_location.lock().await = Some(target); *dragon
}) .target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(target);
} }
} }

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::EnderDragonEntity; use crate::entity::boss::ender_dragon::EnderDragonEntity;
use futures::future::BoxFuture;
pub struct LandingPhase; pub struct LandingPhase;
@ -9,17 +8,22 @@ impl super::Phase for LandingPhase {
EnderDragonPhase::Landing EnderDragonPhase::Landing
} }
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn begin(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { *dragon
*dragon.target_location.lock().await = None; .target_location
}) .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { dragon.set_phase(EnderDragonPhase::SitAttacking);
dragon.set_phase(EnderDragonPhase::SitAttacking).await; *dragon
*dragon.ticks_sitting.lock().await = 0; .ticks_sitting
*dragon.sit_attack_timer.lock().await = 0; .lock()
}) .unwrap_or_else(std::sync::PoisonError::into_inner) = 0;
*dragon
.sit_attack_timer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = 0;
} }
} }

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext}; use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
pub struct LandingApproachPhase; pub struct LandingApproachPhase;
@ -10,21 +9,25 @@ impl super::Phase for LandingApproachPhase {
EnderDragonPhase::LandingApproach EnderDragonPhase::LandingApproach
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let origin = {
let origin = { let guard = dragon
let guard = dragon.fight_origin.lock().await; .fight_origin
guard.0 .lock()
}; .unwrap_or_else(std::sync::PoisonError::into_inner);
let target = Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64); guard.0
let pos = dragon.mob_entity.living_entity.entity.pos.load(); };
let target = Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64);
let pos = dragon.mob_entity.living_entity.entity.pos.load();
if pos.distance_squared(target) < 1.0 { if pos.distance_squared(target) < 1.0 {
dragon.set_phase(EnderDragonPhase::Landing).await; dragon.set_phase(EnderDragonPhase::Landing);
return; return;
} }
*dragon.target_location.lock().await = Some(target); *dragon
}) .target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(target);
} }
} }

View File

@ -1,5 +1,4 @@
use crate::entity::boss::ender_dragon::EnderDragonEntity; use crate::entity::boss::ender_dragon::EnderDragonEntity;
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use std::sync::Arc; use std::sync::Arc;
@ -29,13 +28,9 @@ pub use taking_off::TakingOffPhase;
pub trait Phase: Send + Sync { pub trait Phase: Send + Sync {
fn get_type(&self) -> EnderDragonPhase; fn get_type(&self) -> EnderDragonPhase;
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()>; fn tick(&self, dragon: &EnderDragonEntity);
fn begin<'a>(&'a self, _dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn begin(&self, _dragon: &EnderDragonEntity) {}
Box::pin(async {}) fn end(&self, _dragon: &EnderDragonEntity) {}
}
fn end<'a>(&'a self, _dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn is_sitting(&self) -> bool { fn is_sitting(&self) -> bool {
false false
} }

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::EnderDragonEntity; use crate::entity::boss::ender_dragon::EnderDragonEntity;
use futures::future::BoxFuture;
pub struct SitAttackingPhase; pub struct SitAttackingPhase;
@ -9,39 +8,51 @@ impl super::Phase for SitAttackingPhase {
EnderDragonPhase::SitAttacking EnderDragonPhase::SitAttacking
} }
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn begin(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { *dragon
*dragon.target_location.lock().await = None; .target_location
}) .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let mut timer = dragon
let mut timer = dragon.sit_attack_timer.lock().await; .sit_attack_timer
*timer += 1; .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*timer += 1;
if *timer > 40 { if *timer > 40 {
*timer = 0; *timer = 0;
let should_breathe = rand::random_bool(0.5); let should_breathe = rand::random_bool(0.5);
let should_take_off = *dragon.ticks_sitting.lock().await > 200; let should_take_off = *dragon
drop(timer); .ticks_sitting
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
> 200;
drop(timer);
if should_breathe { if should_breathe {
dragon.set_phase(EnderDragonPhase::SitBreathing).await; dragon.set_phase(EnderDragonPhase::SitBreathing);
*dragon.breathing_timer.lock().await = 0; *dragon
} else if should_take_off { .breathing_timer
dragon.set_phase(EnderDragonPhase::TakingOff).await; .lock()
} .unwrap_or_else(std::sync::PoisonError::into_inner) = 0;
} else { } else if should_take_off {
drop(timer); dragon.set_phase(EnderDragonPhase::TakingOff);
} }
} else {
drop(timer);
}
let mut dmg = dragon.sitting_damage_received.lock().await; let mut dmg = dragon
if *dmg > 150.0 { .sitting_damage_received
*dmg = 0.0; .lock()
drop(dmg); .unwrap_or_else(std::sync::PoisonError::into_inner);
dragon.set_phase(EnderDragonPhase::TakingOff).await; if *dmg > 150.0 {
} *dmg = 0.0;
}) drop(dmg);
dragon.set_phase(EnderDragonPhase::TakingOff);
}
} }
} }

View File

@ -2,7 +2,6 @@ use super::EnderDragonPhase;
use crate::entity::{ use crate::entity::{
Entity, area_effect_cloud::AreaEffectCloudEntity, boss::ender_dragon::EnderDragonEntity, Entity, area_effect_cloud::AreaEffectCloudEntity, boss::ender_dragon::EnderDragonEntity,
}; };
use futures::future::BoxFuture;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -13,61 +12,66 @@ impl super::Phase for SitBreathingPhase {
EnderDragonPhase::SitBreathing EnderDragonPhase::SitBreathing
} }
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn begin(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { *dragon
*dragon.target_location.lock().await = None; .target_location
}) .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let mut timer = dragon
let mut timer = dragon.breathing_timer.lock().await; .breathing_timer
*timer += 1; .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*timer += 1;
if *timer > 100 { if *timer > 100 {
*timer = 0; *timer = 0;
drop(timer);
dragon.set_phase(EnderDragonPhase::SitAttacking).await;
return;
}
drop(timer); drop(timer);
dragon.set_phase(EnderDragonPhase::SitAttacking);
return;
}
drop(timer);
let timer_val = *dragon.breathing_timer.lock().await; let timer_val = *dragon
if timer_val == 1 { .breathing_timer
let entity = &dragon.mob_entity.living_entity.entity; .lock()
let pos = entity.pos.load(); .unwrap_or_else(std::sync::PoisonError::into_inner);
let yaw = entity.yaw.load().to_radians() as f64; if timer_val == 1 {
let world = entity.world.load(); let entity = &dragon.mob_entity.living_entity.entity;
let pos = entity.pos.load();
let yaw = entity.yaw.load().to_radians() as f64;
let world = entity.world.load();
// Spawn the lingering cloud at the dragon's head position // Spawn the lingering cloud at the dragon's head position
let offset = Vector3::new(-yaw.sin() * 2.0, 0.5, yaw.cos() * 2.0); let offset = Vector3::new(-yaw.sin() * 2.0, 0.5, yaw.cos() * 2.0);
let cloud_pos = pos.add(&offset); let cloud_pos = pos.add(&offset);
let cloud_entity = let cloud_entity =
Entity::new(world.clone(), cloud_pos, &EntityType::AREA_EFFECT_CLOUD); Entity::new(world.clone(), cloud_pos, &EntityType::AREA_EFFECT_CLOUD);
let cloud = AreaEffectCloudEntity::create( let cloud = AreaEffectCloudEntity::create(
cloud_entity, cloud_entity,
pumpkin_data::item_stack::ItemStack::new( pumpkin_data::item_stack::ItemStack::new(
0, 0,
&pumpkin_data::item::Item::DRAGON_BREATH, &pumpkin_data::item::Item::DRAGON_BREATH,
), ),
vec![( vec![(
&pumpkin_data::effect::StatusEffect::INSTANT_DAMAGE, &pumpkin_data::effect::StatusEffect::INSTANT_DAMAGE,
1, 1,
0, 0,
false, false,
true, true,
true, true,
)], )],
600, // duration 600, // duration
3.0, // radius 3.0, // radius
20, // reapplication delay 20, // reapplication delay
20, // wait time 20, // wait time
0.5, // radius on use 0.5, // radius on use
-100, // duration on use -100, // duration on use
); );
world.spawn_entity(cloud); world.spawn_entity(cloud);
} }
})
} }
} }

View File

@ -5,7 +5,6 @@ use crate::entity::{
area_effect_cloud::AreaEffectCloudEntity, area_effect_cloud::AreaEffectCloudEntity,
boss::ender_dragon::{EnderDragonEntity, Vector3Ext, find_path}, boss::ender_dragon::{EnderDragonEntity, Vector3Ext, find_path},
}; };
use futures::future::BoxFuture;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -17,153 +16,182 @@ impl super::Phase for StrafingPhase {
EnderDragonPhase::Strafing EnderDragonPhase::Strafing
} }
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn begin(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { *dragon
*dragon.fireball_charge.lock().await = 0; .fireball_charge
*dragon.target_location.lock().await = None; .lock()
}) .unwrap_or_else(std::sync::PoisonError::into_inner) = 0;
*dragon
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let target_id = *dragon
let target_id = *dragon.target_player.lock().await; .target_player
let world = dragon.mob_entity.living_entity.entity.world.load(); .lock()
let pos = dragon.mob_entity.living_entity.entity.pos.load(); .unwrap_or_else(std::sync::PoisonError::into_inner);
let world = dragon.mob_entity.living_entity.entity.world.load();
let pos = dragon.mob_entity.living_entity.entity.pos.load();
let player_target = if let Some(id) = target_id { let player_target = if let Some(id) = target_id {
world world
.players .players
.load() .load()
.iter() .iter()
.find(|p| p.gameprofile.id == id) .find(|p| p.gameprofile.id == id)
.cloned() .cloned()
} else { } else {
None None
}; };
let Some(player) = player_target else { let Some(player) = player_target else {
dragon.set_phase(EnderDragonPhase::Circling).await; dragon.set_phase(EnderDragonPhase::Circling);
return; return;
}; };
let player_pos = player.get_entity().pos.load(); let player_pos = player.get_entity().pos.load();
let mut path = dragon.path.lock().await; let mut path = dragon
let mut target_location = dragon.target_location.lock().await; .path
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut target_location = dragon
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if path.is_empty() {
let d2 = player_pos.x - pos.x;
let d3 = player_pos.z - pos.z;
let d4 = d2.hypot(d3);
let d5 = (0.4 + d4 / 80.0 - 1.0).clamp(0.0, 10.0);
*target_location = Some(Vector3::new(player_pos.x, player_pos.y + d5, player_pos.z));
}
let d11 = target_location
.map(|loc| pos.distance_squared(loc))
.unwrap_or(0.0);
if !(100.0..=22500.0).contains(&d11)
|| dragon
.mob_entity
.living_entity
.entity
.horizontal_collision
.load(Ordering::Relaxed)
{
if path.is_empty() { if path.is_empty() {
let d2 = player_pos.x - pos.x; drop(path);
let d3 = player_pos.z - pos.z; let i = dragon.find_closest_node();
let d4 = d2.hypot(d3); let j = dragon.find_closest_node_to(player_pos);
let d5 = (0.4 + d4 / 80.0 - 1.0).clamp(0.0, 10.0);
*target_location = let mut path_lock = dragon
Some(Vector3::new(player_pos.x, player_pos.y + d5, player_pos.z)); .path
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let nodes = dragon
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*path_lock = find_path(&nodes, i, j, None);
drop(nodes);
path = path_lock;
} }
let d11 = target_location if let Some(next_node_idx) = path.first().copied() {
.map(|loc| pos.distance_squared(loc)) path.remove(0);
.unwrap_or(0.0); let nodes = dragon
if !(100.0..=22500.0).contains(&d11) .nodes
|| dragon .lock()
.mob_entity .unwrap_or_else(std::sync::PoisonError::into_inner);
.living_entity if let Some(node) = nodes[next_node_idx] {
.entity let mut y_target = node.y + rand::random_range(0.0..20.0);
.horizontal_collision while y_target < node.y {
.load(Ordering::Relaxed) y_target = node.y + rand::random_range(0.0..20.0);
{
if path.is_empty() {
drop(path);
let i = dragon.find_closest_node().await;
let j = dragon.find_closest_node_to(player_pos).await;
let mut path_lock = dragon.path.lock().await;
let nodes = dragon.nodes.lock().await;
*path_lock = find_path(&nodes, i, j, None);
drop(nodes);
path = path_lock;
}
if let Some(next_node_idx) = path.first().copied() {
path.remove(0);
let nodes = dragon.nodes.lock().await;
if let Some(node) = nodes[next_node_idx] {
let mut y_target = node.y + rand::random_range(0.0..20.0);
while y_target < node.y {
y_target = node.y + rand::random_range(0.0..20.0);
}
*target_location = Some(Vector3::new(node.x, y_target, node.z));
} }
*target_location = Some(Vector3::new(node.x, y_target, node.z));
} }
} }
drop(path); }
drop(target_location); drop(path);
drop(target_location);
if player_pos.distance_squared(pos) < 4096.0 { if player_pos.distance_squared(pos) < 4096.0 {
let mut charge = dragon.fireball_charge.lock().await; let mut charge = dragon
.fireball_charge
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let aim_diff = player_pos - pos; let aim_diff = player_pos - pos;
let aim = if aim_diff.length_squared() > 1e-6 { let aim = if aim_diff.length_squared() > 1e-6 {
aim_diff.normalize() aim_diff.normalize()
} else {
Vector3::new(0.0, 0.0, 0.0)
};
let yaw = dragon.mob_entity.living_entity.entity.yaw.load();
let dir = Vector3::new(
(yaw * (std::f32::consts::PI / 180.0)).sin() as f64,
0.0,
-(yaw * (std::f32::consts::PI / 180.0)).cos() as f64,
);
let dir_norm = if dir.length_squared() > 1e-6 {
dir.normalize()
} else {
Vector3::new(0.0, 0.0, 0.0)
};
let dot = dir_norm.dot(&aim) as f32;
let angle_degs = dot.acos().to_degrees() + 0.5;
*charge += 1;
if *charge >= 5 && angle_degs < 10.0 {
*charge = 0;
drop(charge);
let cloud_entity =
Entity::new(world.clone(), player_pos, &EntityType::AREA_EFFECT_CLOUD);
let cloud = AreaEffectCloudEntity::create(
cloud_entity,
pumpkin_data::item_stack::ItemStack::new(
0,
&pumpkin_data::item::Item::DRAGON_BREATH,
),
vec![(
&pumpkin_data::effect::StatusEffect::INSTANT_DAMAGE,
1,
0,
false,
true,
true,
)],
600,
3.0,
20,
20,
0.5,
-100,
);
world.spawn_entity(cloud);
dragon.path.lock().await.clear();
dragon.set_phase(EnderDragonPhase::Circling).await;
}
} else { } else {
let mut charge = dragon.fireball_charge.lock().await; Vector3::new(0.0, 0.0, 0.0)
if *charge > 0 { };
*charge -= 1;
} let yaw = dragon.mob_entity.living_entity.entity.yaw.load();
let dir = Vector3::new(
(yaw * (std::f32::consts::PI / 180.0)).sin() as f64,
0.0,
-(yaw * (std::f32::consts::PI / 180.0)).cos() as f64,
);
let dir_norm = if dir.length_squared() > 1e-6 {
dir.normalize()
} else {
Vector3::new(0.0, 0.0, 0.0)
};
let dot = dir_norm.dot(&aim) as f32;
let angle_degs = dot.acos().to_degrees() + 0.5;
*charge += 1;
if *charge >= 5 && angle_degs < 10.0 {
*charge = 0;
drop(charge);
let cloud_entity =
Entity::new(world.clone(), player_pos, &EntityType::AREA_EFFECT_CLOUD);
let cloud = AreaEffectCloudEntity::create(
cloud_entity,
pumpkin_data::item_stack::ItemStack::new(
0,
&pumpkin_data::item::Item::DRAGON_BREATH,
),
vec![(
&pumpkin_data::effect::StatusEffect::INSTANT_DAMAGE,
1,
0,
false,
true,
true,
)],
600,
3.0,
20,
20,
0.5,
-100,
);
world.spawn_entity(cloud);
dragon
.path
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clear();
dragon.set_phase(EnderDragonPhase::Circling);
} }
}) } else {
let mut charge = dragon
.fireball_charge
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if *charge > 0 {
*charge -= 1;
}
}
} }
} }

View File

@ -1,6 +1,5 @@
use super::EnderDragonPhase; use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y, Vector3Ext}; use crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
pub struct TakingOffPhase; pub struct TakingOffPhase;
@ -10,21 +9,25 @@ impl super::Phase for TakingOffPhase {
EnderDragonPhase::TakingOff EnderDragonPhase::TakingOff
} }
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> { fn tick(&self, dragon: &EnderDragonEntity) {
Box::pin(async move { let origin = {
let origin = { let guard = dragon
let guard = dragon.fight_origin.lock().await; .fight_origin
guard.0 .lock()
}; .unwrap_or_else(std::sync::PoisonError::into_inner);
let target = Vector3::new(origin.x as f64, NODE_Y as f64, origin.z as f64); guard.0
let pos = dragon.mob_entity.living_entity.entity.pos.load(); };
let target = Vector3::new(origin.x as f64, NODE_Y as f64, origin.z as f64);
let pos = dragon.mob_entity.living_entity.entity.pos.load();
if pos.distance_squared(target) < 16.0 { if pos.distance_squared(target) < 16.0 {
dragon.set_phase(EnderDragonPhase::Circling).await; dragon.set_phase(EnderDragonPhase::Circling);
return; return;
} }
*dragon.target_location.lock().await = Some(target); *dragon
}) .target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(target);
} }
} }

View File

@ -1,8 +1,7 @@
use std::sync::{ use std::sync::{
Arc, Weak, Arc, Mutex, Weak,
atomic::{AtomicBool, AtomicI32, Ordering}, atomic::{AtomicBool, AtomicI32, Ordering},
}; };
use tokio::sync::Mutex;
use uuid::Uuid; use uuid::Uuid;
use pumpkin_data::{ use pumpkin_data::{
@ -26,7 +25,7 @@ use pumpkin_world::world::BlockFlags;
use crate::{ use crate::{
entity::{ entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal,
revenge::RevengeGoal, revenge::RevengeGoal,
@ -259,7 +258,7 @@ impl WitherEntity {
} }
} }
async fn update_bossbar(&self, world: &Arc<crate::world::World>, progress: f32) { fn update_bossbar(&self, world: &Arc<crate::world::World>, progress: f32) {
let pos = self.mob_entity.living_entity.entity.pos.load(); let pos = self.mob_entity.living_entity.entity.pos.load();
let tracking_radius_sq = 50.0 * 50.0; let tracking_radius_sq = 50.0 * 50.0;
let players = world.players.load(); let players = world.players.load();
@ -273,7 +272,10 @@ impl WitherEntity {
.map(|p| p.gameprofile.id) .map(|p| p.gameprofile.id)
.collect(); .collect();
let mut bossbar_players = self.bossbar_players.lock().await; let mut bossbar_players = self
.bossbar_players
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for &uid in &current { for &uid in &current {
if !bossbar_players.contains(&uid) { if !bossbar_players.contains(&uid) {
@ -306,8 +308,11 @@ impl WitherEntity {
} }
} }
async fn remove_all_bossbar(&self, world: &Arc<crate::world::World>) { fn remove_all_bossbar(&self, world: &Arc<crate::world::World>) {
let mut bossbar_players = self.bossbar_players.lock().await; let mut bossbar_players = self
.bossbar_players
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let players = world.players.load(); let players = world.players.load();
for player in players.iter() { for player in players.iter() {
if bossbar_players.contains(&player.gameprofile.id) { if bossbar_players.contains(&player.gameprofile.id) {
@ -318,18 +323,18 @@ impl WitherEntity {
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
async fn async_mob_tick(&self) { fn tick_wither(&self) {
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
let world = entity.world.load(); let world = entity.world.load();
if world.level_info.load().difficulty == Difficulty::Peaceful { if world.level_info.load().difficulty == Difficulty::Peaceful {
self.remove_all_bossbar(&world).await; self.remove_all_bossbar(&world);
entity.remove(); entity.remove();
return; return;
} }
if !entity.is_alive() || self.mob_entity.living_entity.health.load() <= 0.0 { if !entity.is_alive() || self.mob_entity.living_entity.health.load() <= 0.0 {
self.remove_all_bossbar(&world).await; self.remove_all_bossbar(&world);
if !self.dropped_loot.swap(true, Ordering::SeqCst) { if !self.dropped_loot.swap(true, Ordering::SeqCst) {
let pos = entity.block_pos.load(); let pos = entity.block_pos.load();
world.drop_stack(&pos, ItemStack::new(1, &Item::NETHER_STAR)); world.drop_stack(&pos, ItemStack::new(1, &Item::NETHER_STAR));
@ -343,18 +348,16 @@ impl WitherEntity {
if invul > 0 { if invul > 0 {
let new_count = invul - 1; let new_count = invul - 1;
let progress = (1.0 - (new_count as f32) / 220.0).clamp(0.0, 1.0); let progress = (1.0 - (new_count as f32) / 220.0).clamp(0.0, 1.0);
self.update_bossbar(&world, progress).await; self.update_bossbar(&world, progress);
if new_count <= 0 { if new_count <= 0 {
let pos = entity.pos.load(); let pos = entity.pos.load();
let eye_y = pos.y + entity.get_eye_height(); let eye_y = pos.y + entity.get_eye_height();
world world.explode(
.explode( Vector3::new(pos.x, eye_y, pos.z),
Vector3::new(pos.x, eye_y, pos.z), 7.0,
7.0, ExplosionInteraction::Mob,
ExplosionInteraction::Mob, );
)
.await;
if !entity.silent.load(Ordering::Relaxed) { if !entity.silent.load(Ordering::Relaxed) {
world.sync_world_event( world.sync_world_event(
@ -378,7 +381,7 @@ impl WitherEntity {
} else { } else {
0.0 0.0
}; };
self.update_bossbar(&world, progress).await; self.update_bossbar(&world, progress);
if tick_count % 20 == 0 { if tick_count % 20 == 0 {
living.heal(1.0); living.heal(1.0);
@ -604,17 +607,7 @@ impl Mob for WitherEntity {
} }
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) { fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
let entity_id = self.mob_entity.living_entity.entity.entity_id; self.tick_wither();
let world = self.mob_entity.living_entity.entity.world.load_full();
tokio::spawn(async move {
let Some(entity) = world.get_entity_by_id(entity_id) else {
return;
};
let Some(wither) = entity.cast_any().downcast_ref::<Self>() else {
return;
};
wither.async_mob_tick().await;
});
} }
fn pre_damage(&self, damage_type: DamageType, source: Option<&dyn EntityBase>) -> bool { fn pre_damage(&self, damage_type: DamageType, source: Option<&dyn EntityBase>) -> bool {
@ -662,35 +655,22 @@ impl Mob for WitherEntity {
fn post_tick(&self) { fn post_tick(&self) {
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() || self.mob_entity.living_entity.health.load() <= 0.0 { if !entity.is_alive() || self.mob_entity.living_entity.health.load() <= 0.0 {
let entity_id = entity.entity_id;
let world = entity.world.load_full(); let world = entity.world.load_full();
tokio::spawn(async move { self.remove_all_bossbar(&world);
let Some(entity) = world.get_entity_by_id(entity_id) else { if !self.dropped_loot.swap(true, Ordering::SeqCst) {
return; let pos = entity.get_entity().block_pos.load();
}; world.drop_stack(&pos, ItemStack::new(1, &Item::NETHER_STAR));
let Some(wither) = entity.cast_any().downcast_ref::<Self>() else { }
return;
};
wither.remove_all_bossbar(&world).await;
if !wither.dropped_loot.swap(true, Ordering::SeqCst) {
let pos = entity.get_entity().block_pos.load();
world.drop_stack(&pos, ItemStack::new(1, &Item::NETHER_STAR));
}
});
} }
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { nbt.put_int("Invul", self.get_invulnerable_ticks());
nbt.put_int("Invul", self.get_invulnerable_ticks());
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(invul) = nbt.get_int("Invul") {
if let Some(invul) = nbt.get_int("Invul") { self.set_invulnerable_ticks(invul);
self.set_invulnerable_ticks(invul); }
}
})
} }
} }

View File

@ -74,9 +74,7 @@ impl BreathManager {
player.entity_id(), player.entity_id(),
new_air, new_air,
); );
tokio::spawn(async move { server.plugin_manager.fire_blocking(&server, &mut event);
server.plugin_manager.fire(&server, &mut event).await;
});
} }
self.send_air_supply(player); self.send_air_supply(player);
} }

View File

@ -1,6 +1,6 @@
use std::sync::atomic::{AtomicI32, AtomicI64, AtomicU8, Ordering}; use std::sync::atomic::{AtomicI32, AtomicI64, AtomicU8, Ordering};
use crate::entity::{Entity, EntityBase, EntityBaseFuture, NbtFuture, living::LivingEntity}; use crate::entity::{Entity, EntityBase, living::LivingEntity};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::{ use pumpkin_data::{
@ -230,72 +230,68 @@ impl ArmorStandEntity {
} }
impl EntityBase for ArmorStandEntity { impl EntityBase for ArmorStandEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async { let disabled_slots = self.disabled_slots.load(Ordering::Relaxed);
let disabled_slots = self.disabled_slots.load(Ordering::Relaxed); // ...
// ...
nbt.put_bool("Invisible", self.is_invisible()); nbt.put_bool("Invisible", self.is_invisible());
nbt.put_bool("Small", self.is_small()); nbt.put_bool("Small", self.is_small());
nbt.put_bool("ShowArms", self.should_show_arms()); nbt.put_bool("ShowArms", self.should_show_arms());
nbt.put_int("DisabledSlots", disabled_slots); nbt.put_int("DisabledSlots", disabled_slots);
nbt.put_bool("NoBasePlate", !self.should_show_base_plate()); nbt.put_bool("NoBasePlate", !self.should_show_base_plate());
if self.is_marker() { if self.is_marker() {
nbt.put_bool("Marker", true); nbt.put_bool("Marker", true);
} }
nbt.put("Pose", self.pack_rotation()); nbt.put("Pose", self.pack_rotation());
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async { let mut flags = 0u8;
let mut flags = 0u8; // ...
// ...
if let Some(invisible) = nbt.get_bool("Invisible") if let Some(invisible) = nbt.get_bool("Invisible")
&& invisible && invisible
{ {
self.get_entity().set_invisible(invisible); self.get_entity().set_invisible(invisible);
} }
if let Some(small) = nbt.get_bool("Small") if let Some(small) = nbt.get_bool("Small")
&& small && small
{ {
flags |= ArmorStandFlags::Small as u8; flags |= ArmorStandFlags::Small as u8;
} }
if let Some(show_arms) = nbt.get_bool("ShowArms") if let Some(show_arms) = nbt.get_bool("ShowArms")
&& show_arms && show_arms
{ {
flags |= ArmorStandFlags::ShowArms as u8; flags |= ArmorStandFlags::ShowArms as u8;
} }
if let Some(disabled_slots) = nbt.get_int("DisabledSlots") { if let Some(disabled_slots) = nbt.get_int("DisabledSlots") {
self.disabled_slots.store(disabled_slots, Ordering::Relaxed); self.disabled_slots.store(disabled_slots, Ordering::Relaxed);
} }
if let Some(no_base_plate) = nbt.get_bool("NoBasePlate") { if let Some(no_base_plate) = nbt.get_bool("NoBasePlate") {
if !no_base_plate { if !no_base_plate {
flags |= ArmorStandFlags::HideBasePlate as u8;
}
} else {
flags |= ArmorStandFlags::HideBasePlate as u8; flags |= ArmorStandFlags::HideBasePlate as u8;
} }
} else {
flags |= ArmorStandFlags::HideBasePlate as u8;
}
if let Some(marker) = nbt.get_bool("Marker") if let Some(marker) = nbt.get_bool("Marker")
&& marker && marker
{ {
flags |= ArmorStandFlags::Marker as u8; flags |= ArmorStandFlags::Marker as u8;
} }
self.armor_stand_flags.store(flags, Ordering::Relaxed); self.armor_stand_flags.store(flags, Ordering::Relaxed);
if let Some(pose_tag) = nbt.get("Pose") { if let Some(pose_tag) = nbt.get("Pose") {
let packed: PackedRotation = pose_tag.clone().into(); let packed: PackedRotation = pose_tag.clone().into();
self.unpack_rotation(&packed); self.unpack_rotation(&packed);
} }
})
} }
fn get_entity(&self) -> &Entity { fn get_entity(&self) -> &Entity {
@ -306,11 +302,9 @@ impl EntityBase for ArmorStandEntity {
Some(&self.living_entity) Some(&self.living_entity)
} }
fn kill<'a>(&'a self, _caller: &'a dyn EntityBase) -> EntityBaseFuture<'a, ()> { fn kill(&self, _caller: &dyn EntityBase) {
Box::pin(async move { self.get_entity().remove();
self.get_entity().remove(); // TODO: emit GameEvent::ENTITY_DIE
// TODO: emit GameEvent::ENTITY_DIE
})
} }
fn damage_with_context( fn damage_with_context(

View File

@ -13,7 +13,7 @@ use pumpkin_protocol::{
use pumpkin_util::{math::vector3::Vector3, text::TextComponent}; use pumpkin_util::{math::vector3::Vector3, text::TextComponent};
use crate::{ use crate::{
entity::{Entity, EntityBase, NbtFuture, living::LivingEntity}, entity::{Entity, EntityBase, living::LivingEntity},
server::Server, server::Server,
}; };
@ -796,20 +796,16 @@ impl BlockDisplayEntity {
} }
impl EntityBase for BlockDisplayEntity { impl EntityBase for BlockDisplayEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.display.write_display_nbt(nbt);
self.display.write_display_nbt(nbt); nbt.put_int("block_state", self.block_state.load(Ordering::Relaxed));
nbt.put_int("block_state", self.block_state.load(Ordering::Relaxed));
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.display.read_display_nbt(nbt);
self.display.read_display_nbt(nbt); if let Some(state) = nbt.get_int("block_state") {
if let Some(state) = nbt.get_int("block_state") { self.block_state.store(state, Ordering::Relaxed);
self.block_state.store(state, Ordering::Relaxed); }
}
})
} }
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {} fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {}
@ -919,42 +915,38 @@ impl ItemDisplayEntity {
} }
impl EntityBase for ItemDisplayEntity { impl EntityBase for ItemDisplayEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.display.write_display_nbt(nbt);
self.display.write_display_nbt(nbt); let display_mode_str = match self.item_display.load(Ordering::Relaxed) {
let display_mode_str = match self.item_display.load(Ordering::Relaxed) { 1 => "thirdperson_lefthand",
1 => "thirdperson_lefthand", 2 => "thirdperson_righthand",
2 => "thirdperson_righthand", 3 => "firstperson_lefthand",
3 => "firstperson_lefthand", 4 => "firstperson_righthand",
4 => "firstperson_righthand", 5 => "head",
5 => "head", 6 => "gui",
6 => "gui", 7 => "ground",
7 => "ground", 8 => "fixed",
8 => "fixed", _ => "none",
_ => "none", };
}; nbt.put_string("item_display", display_mode_str.to_string());
nbt.put_string("item_display", display_mode_str.to_string());
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.display.read_display_nbt(nbt);
self.display.read_display_nbt(nbt); if let Some(mode_str) = nbt.get_string("item_display") {
if let Some(mode_str) = nbt.get_string("item_display") { let mode = match mode_str {
let mode = match mode_str { "thirdperson_lefthand" => 1,
"thirdperson_lefthand" => 1, "thirdperson_righthand" => 2,
"thirdperson_righthand" => 2, "firstperson_lefthand" => 3,
"firstperson_lefthand" => 3, "firstperson_righthand" => 4,
"firstperson_righthand" => 4, "head" => 5,
"head" => 5, "gui" => 6,
"gui" => 6, "ground" => 7,
"ground" => 7, "fixed" => 8,
"fixed" => 8, _ => 0,
_ => 0, };
}; self.item_display.store(mode, Ordering::Relaxed);
self.item_display.store(mode, Ordering::Relaxed); }
}
})
} }
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {} fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {}
@ -1203,77 +1195,73 @@ impl TextDisplayEntity {
} }
impl EntityBase for TextDisplayEntity { impl EntityBase for TextDisplayEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.display.write_display_nbt(nbt);
self.display.write_display_nbt(nbt); let text_json_res = pumpkin_util::serde_json::to_string(
let text_json_res = pumpkin_util::serde_json::to_string( &*self
&*self .text
.text .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner),
.unwrap_or_else(std::sync::PoisonError::into_inner), );
); if let Ok(text_json) = text_json_res {
if let Ok(text_json) = text_json_res { nbt.put_string("text", text_json);
nbt.put_string("text", text_json); }
} nbt.put_int("line_width", self.line_width.load(Ordering::Relaxed));
nbt.put_int("line_width", self.line_width.load(Ordering::Relaxed)); nbt.put_int("background", self.background.load(Ordering::Relaxed));
nbt.put_int("background", self.background.load(Ordering::Relaxed)); nbt.put_byte("text_opacity", self.text_opacity.load(Ordering::Relaxed));
nbt.put_byte("text_opacity", self.text_opacity.load(Ordering::Relaxed));
let flags = self.flags.load(Ordering::Relaxed); let flags = self.flags.load(Ordering::Relaxed);
nbt.put_bool("shadow", flags & 1 != 0); nbt.put_bool("shadow", flags & 1 != 0);
nbt.put_bool("see_through", flags & 2 != 0); nbt.put_bool("see_through", flags & 2 != 0);
nbt.put_bool("default_background", flags & 4 != 0); nbt.put_bool("default_background", flags & 4 != 0);
let align_str = if flags & 8 != 0 { let align_str = if flags & 8 != 0 {
"left" "left"
} else if flags & 16 != 0 { } else if flags & 16 != 0 {
"right" "right"
} else { } else {
"center" "center"
}; };
nbt.put_string("alignment", align_str.to_string()); nbt.put_string("alignment", align_str.to_string());
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.display.read_display_nbt(nbt);
self.display.read_display_nbt(nbt); if let Some(text_json) = nbt.get_string("text")
if let Some(text_json) = nbt.get_string("text") && let Ok(component) = pumpkin_util::serde_json::from_str(text_json)
&& let Ok(component) = pumpkin_util::serde_json::from_str(text_json) {
{ *self
*self .text
.text .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = component;
.unwrap_or_else(std::sync::PoisonError::into_inner) = component; }
} if let Some(lw) = nbt.get_int("line_width") {
if let Some(lw) = nbt.get_int("line_width") { self.line_width.store(lw, Ordering::Relaxed);
self.line_width.store(lw, Ordering::Relaxed); }
} if let Some(bg) = nbt.get_int("background") {
if let Some(bg) = nbt.get_int("background") { self.background.store(bg, Ordering::Relaxed);
self.background.store(bg, Ordering::Relaxed); }
} if let Some(opacity) = nbt.get_byte("text_opacity") {
if let Some(opacity) = nbt.get_byte("text_opacity") { self.text_opacity.store(opacity, Ordering::Relaxed);
self.text_opacity.store(opacity, Ordering::Relaxed); }
}
let mut flags = 0u8; let mut flags = 0u8;
if nbt.get_bool("shadow").unwrap_or(false) { if nbt.get_bool("shadow").unwrap_or(false) {
flags |= 1; flags |= 1;
}
if nbt.get_bool("see_through").unwrap_or(false) {
flags |= 2;
}
if nbt.get_bool("default_background").unwrap_or(false) {
flags |= 4;
}
if let Some(align) = nbt.get_string("alignment") {
match align {
"left" => flags |= 8,
"right" => flags |= 16,
_ => {}
} }
if nbt.get_bool("see_through").unwrap_or(false) { }
flags |= 2; self.flags.store(flags, Ordering::Relaxed);
}
if nbt.get_bool("default_background").unwrap_or(false) {
flags |= 4;
}
if let Some(align) = nbt.get_string("alignment") {
match align {
"left" => flags |= 8,
"right" => flags |= 16,
_ => {}
}
}
self.flags.store(flags, Ordering::Relaxed);
})
} }
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {} fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {}

View File

@ -52,11 +52,7 @@ impl EntityBase for EndCrystalEntity {
if !damage_type.has_tag(&tag::DamageType::MINECRAFT_IS_EXPLOSION) { if !damage_type.has_tag(&tag::DamageType::MINECRAFT_IS_EXPLOSION) {
let world = self.entity.world.load(); let world = self.entity.world.load();
let pos = self.entity.pos.load(); let pos = self.entity.pos.load();
tokio::spawn(async move { world.explode(pos, 6.0, crate::world::ExplosionInteraction::Block);
world
.explode(pos, 6.0, crate::world::ExplosionInteraction::Block)
.await;
});
} }
// TODO // TODO

View File

@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::entity::{Entity, EntityBase, EntityBaseFuture, NbtFuture, living::LivingEntity}; use crate::entity::{Entity, EntityBase, EntityBaseFuture, living::LivingEntity};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use pumpkin_data::BlockDirection; use pumpkin_data::BlockDirection;
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
@ -282,52 +282,48 @@ impl ItemFrameEntity {
} }
impl EntityBase for ItemFrameEntity { impl EntityBase for ItemFrameEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { let item = self
let item = self .item_stack
.item_stack .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(std::sync::PoisonError::into_inner); if !item.is_empty() {
if !item.is_empty() { let mut item_compound = NbtCompound::new();
let mut item_compound = NbtCompound::new(); item.write_item_stack(&mut item_compound);
item.write_item_stack(&mut item_compound); nbt.put_compound("Item", item_compound);
nbt.put_compound("Item", item_compound); }
} nbt.put_float("ItemDropChance", self.item_drop_chance.load());
nbt.put_float("ItemDropChance", self.item_drop_chance.load()); nbt.put_byte("ItemRotation", self.rotation.load(Ordering::Relaxed) as i8);
nbt.put_byte("ItemRotation", self.rotation.load(Ordering::Relaxed) as i8); nbt.put_byte("Facing", self.facing.load(Ordering::Relaxed) as i8);
nbt.put_byte("Facing", self.facing.load(Ordering::Relaxed) as i8); nbt.put_bool("Invisible", self.invisible.load(Ordering::Relaxed));
nbt.put_bool("Invisible", self.invisible.load(Ordering::Relaxed)); nbt.put_bool("Fixed", self.fixed.load(Ordering::Relaxed));
nbt.put_bool("Fixed", self.fixed.load(Ordering::Relaxed));
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async { if let Some(item_compound) = nbt.get_compound("Item")
if let Some(item_compound) = nbt.get_compound("Item") && let Some(stack) = ItemStack::read_item_stack(item_compound)
&& let Some(stack) = ItemStack::read_item_stack(item_compound) {
{ *self
*self .item_stack
.item_stack .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack; }
} self.rotation.store(
self.rotation.store( (nbt.get_byte("ItemRotation").unwrap_or(0) as u8) % 8,
(nbt.get_byte("ItemRotation").unwrap_or(0) as u8) % 8, Ordering::Relaxed,
Ordering::Relaxed, );
); let facing = nbt.get_byte("Facing").unwrap_or(0) as u8 % 6;
let facing = nbt.get_byte("Facing").unwrap_or(0) as u8 % 6; self.facing.store(facing, Ordering::Relaxed);
self.facing.store(facing, Ordering::Relaxed); // The spawn packet's data field carries the frame's direction.
// The spawn packet's data field carries the frame's direction. self.entity.data.store(i32::from(facing), Ordering::Relaxed);
self.entity.data.store(i32::from(facing), Ordering::Relaxed); self.item_drop_chance
self.item_drop_chance .store(nbt.get_float("ItemDropChance").unwrap_or(1.0));
.store(nbt.get_float("ItemDropChance").unwrap_or(1.0)); self.invisible.store(
self.invisible.store( nbt.get_bool("Invisible").unwrap_or(false),
nbt.get_bool("Invisible").unwrap_or(false), Ordering::Relaxed,
Ordering::Relaxed, );
); self.fixed
self.fixed .store(nbt.get_bool("Fixed").unwrap_or(false), Ordering::Relaxed);
.store(nbt.get_bool("Fixed").unwrap_or(false), Ordering::Relaxed);
})
} }
fn get_entity(&self) -> &Entity { fn get_entity(&self) -> &Entity {
@ -405,37 +401,31 @@ impl EntityBase for ItemFrameEntity {
}) })
} }
fn interact<'a>( fn interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
&'a self, if self.is_fixed() {
player: &'a Arc<Player>, return false;
item_stack: &'a mut ItemStack, }
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { let frame_has_item = !self.get_item().is_empty();
if self.is_fixed() { let has_held_item = !item_stack.is_empty();
return false;
if frame_has_item {
let new_rot = self.get_rotation() + 1;
self.set_rotation(new_rot, true);
self.entity.play_sound(self.get_rotate_item_sound());
true
} else if has_held_item && !self.entity.removed.load(Ordering::Relaxed) {
let mut new_stack = item_stack.clone();
new_stack.item_count = 1;
self.set_item(new_stack, true);
if !player.is_creative() {
item_stack.decrement(1);
} }
true
let frame_has_item = !self.get_item().is_empty(); } else {
let has_held_item = !item_stack.is_empty(); false
}
if frame_has_item {
let new_rot = self.get_rotation() + 1;
self.set_rotation(new_rot, true);
self.entity.play_sound(self.get_rotate_item_sound());
true
} else if has_held_item && !self.entity.removed.load(Ordering::Relaxed) {
let mut new_stack = item_stack.clone();
new_stack.item_count = 1;
self.set_item(new_stack, true);
if !player.is_creative() {
item_stack.decrement(1);
}
true
} else {
false
}
})
} }
fn damage_with_context( fn damage_with_context(

View File

@ -1,5 +1,5 @@
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::entity::{Entity, EntityBase, EntityBaseFuture, living::LivingEntity}; use crate::entity::{Entity, EntityBase, living::LivingEntity};
use crate::world::World; use crate::world::World;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
@ -135,66 +135,60 @@ impl EntityBase for LeashKnotEntity {
} }
} }
fn interact<'a>( fn interact(&self, player: &Arc<Player>, _item_stack: &mut ItemStack) -> bool {
&'a self, let world = player.world();
player: &'a Arc<Player>, let knot_id = self.entity.entity_id;
_item_stack: &'a mut ItemStack, let player_id = player.entity_id();
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move {
let world = player.world();
let knot_id = self.entity.entity_id;
let player_id = player.entity_id();
let search_dim = EntityDimensions { let search_dim = EntityDimensions {
width: 32.0, width: 32.0,
height: 32.0, height: 32.0,
eye_height: 16.0, eye_height: 16.0,
}; };
let pos = self.entity.pos.load(); let pos = self.entity.pos.load();
let search_box = BoundingBox::new_from_pos(pos.x, pos.y, pos.z, &search_dim); let search_box = BoundingBox::new_from_pos(pos.x, pos.y, pos.z, &search_dim);
let entities = world.get_entities_at_box(&search_box); let entities = world.get_entities_at_box(&search_box);
let mut attached_mob = false; let mut attached_mob = false;
let mut player_leashed_mobs = Vec::new(); let mut player_leashed_mobs = Vec::new();
for entity_base in &entities {
let ent = entity_base.get_entity();
if let Ok(guard) = ent.leashed_to.try_lock()
&& let Some(holder) = guard.as_ref()
&& holder.get_entity().entity_id == player_id
{
player_leashed_mobs.push(ent);
}
}
if let Some(self_knot) = Self::get_knot(&world, self.pos) {
for mob in player_leashed_mobs {
mob.leash_to(self_knot.clone() as Arc<dyn EntityBase>);
attached_mob = true;
}
}
let mut any_dropped = false;
if !attached_mob {
for entity_base in &entities { for entity_base in &entities {
let ent = entity_base.get_entity(); let ent = entity_base.get_entity();
if let Ok(guard) = ent.leashed_to.try_lock() if let Ok(guard) = ent.leashed_to.try_lock()
&& let Some(holder) = guard.as_ref() && let Some(holder) = guard.as_ref()
&& holder.get_entity().entity_id == player_id && holder.get_entity().entity_id == knot_id
{ {
player_leashed_mobs.push(ent); ent.leash_to(player.clone() as Arc<dyn EntityBase>);
any_dropped = true;
} }
} }
}
if let Some(self_knot) = Self::get_knot(&world, self.pos) { if attached_mob || any_dropped {
for mob in player_leashed_mobs { self.play_placement_sound(&world);
mob.leash_to(self_knot.clone() as Arc<dyn EntityBase>); true
attached_mob = true; } else {
} false
} }
let mut any_dropped = false;
if !attached_mob {
for entity_base in &entities {
let ent = entity_base.get_entity();
if let Ok(guard) = ent.leashed_to.try_lock()
&& let Some(holder) = guard.as_ref()
&& holder.get_entity().entity_id == knot_id
{
ent.leash_to(player.clone() as Arc<dyn EntityBase>);
any_dropped = true;
}
}
}
if attached_mob || any_dropped {
self.play_placement_sound(&world);
true
} else {
false
}
})
} }
fn cast_any(&self) -> &dyn std::any::Any { fn cast_any(&self) -> &dyn std::any::Any {
self self

View File

@ -1,7 +1,7 @@
use core::f32; use core::f32;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::entity::{Entity, EntityBase, NbtFuture, living::LivingEntity}; use crate::entity::{Entity, EntityBase, living::LivingEntity};
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -17,17 +17,13 @@ impl PaintingEntity {
} }
impl EntityBase for PaintingEntity { impl EntityBase for PaintingEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async { nbt.put_byte("facing", self.entity.data.load(Ordering::Relaxed) as i8);
nbt.put_byte("facing", self.entity.data.load(Ordering::Relaxed) as i8);
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async { let facing = nbt.get_byte("facing").unwrap_or(3);
let facing = nbt.get_byte("facing").unwrap_or(3); self.entity.data.store(facing as i32, Ordering::Relaxed);
self.entity.data.store(facing as i32, Ordering::Relaxed);
})
} }
fn get_entity(&self) -> &Entity { fn get_entity(&self) -> &Entity {

View File

@ -1,4 +1,4 @@
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct HungerMobEffect; pub struct HungerMobEffect;
@ -11,19 +11,13 @@ impl MobEffect for HungerMobEffect {
(duration as u32).is_multiple_of(20) (duration as u32).is_multiple_of(20)
} }
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, living: &LivingEntity, amplifier: u8) {
&'a self, let world = living.entity.world.load();
living: &'a LivingEntity, if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
amplifier: u8, && let Some(player) = entity.get_player()
) -> EffectFuture<'a, ()> { {
Box::pin(async move { let exhaustion = 0.1 * (f32::from(amplifier) + 1.0);
let world = living.entity.world.load(); player.hunger_manager.add_exhaustion(exhaustion);
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id) }
&& let Some(player) = entity.get_player()
{
let exhaustion = 0.1 * (f32::from(amplifier) + 1.0);
player.hunger_manager.add_exhaustion(exhaustion);
}
})
} }
} }

View File

@ -5,69 +5,65 @@ use pumpkin_data::entity::EntityType;
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::r#type::from_type; use crate::entity::r#type::from_type;
pub struct InfestedMobEffect; pub struct InfestedMobEffect;
impl MobEffect for InfestedMobEffect { impl MobEffect for InfestedMobEffect {
fn on_mob_hurt<'a>( fn on_mob_hurt(
&'a self, &self,
living: &'a LivingEntity, living: &LivingEntity,
_amplifier: u8, _amplifier: u8,
_damage_type: &'a DamageType, _damage_type: &DamageType,
_damage_amount: f32, _damage_amount: f32,
) -> EffectFuture<'a, ()> { ) {
Box::pin(async move { // Wither, ender dragon and silverfish are immune
// Wither, ender dragon and silverfish are immune if living.entity.entity_type == &EntityType::WITHER
if living.entity.entity_type == &EntityType::WITHER || living.entity.entity_type == &EntityType::ENDER_DRAGON
|| living.entity.entity_type == &EntityType::ENDER_DRAGON || living.entity.entity_type == &EntityType::SILVERFISH
|| living.entity.entity_type == &EntityType::SILVERFISH {
{ return;
return; }
let world = living.entity.world.load();
// 10% chance to spawn
if rand::random::<f32>() <= 0.1 {
let count = rand::random::<u32>() % 2 + 1;
let bbox = living.entity.bounding_box.load();
let center = Vector3::new(
f64::midpoint(bbox.min.x, bbox.max.x),
f64::midpoint(bbox.min.y, bbox.max.y),
f64::midpoint(bbox.min.z, bbox.max.z),
);
let rot = living.entity.rotation();
let vx = rot.x * 0.3;
let vy = rot.y * 0.45;
let vz = rot.z * 0.3;
for _ in 0..count {
let random_angle = (rand::random::<f32>() - 0.5) * std::f32::consts::PI;
let cos_a = random_angle.cos();
let sin_a = random_angle.sin();
let rx = vx * cos_a + vz * sin_a;
let rz = -vx * sin_a + vz * cos_a;
let silver = from_type(&EntityType::SILVERFISH, center, &world, Uuid::new_v4());
let silver_entity = silver.get_entity();
silver_entity.set_pos(center);
silver_entity.velocity.store(Vector3::new(
f64::from(rx),
f64::from(vy),
f64::from(rz),
));
world.spawn_entity(silver);
} }
let world = living.entity.world.load(); world.play_sound(Sound::EntitySilverfishHurt, SoundCategory::Hostile, &center);
}
// 10% chance to spawn
if rand::random::<f32>() <= 0.1 {
let count = rand::random::<u32>() % 2 + 1;
let bbox = living.entity.bounding_box.load();
let center = Vector3::new(
f64::midpoint(bbox.min.x, bbox.max.x),
f64::midpoint(bbox.min.y, bbox.max.y),
f64::midpoint(bbox.min.z, bbox.max.z),
);
let rot = living.entity.rotation();
let vx = rot.x * 0.3;
let vy = rot.y * 0.45;
let vz = rot.z * 0.3;
for _ in 0..count {
let random_angle = (rand::random::<f32>() - 0.5) * std::f32::consts::PI;
let cos_a = random_angle.cos();
let sin_a = random_angle.sin();
let rx = vx * cos_a + vz * sin_a;
let rz = -vx * sin_a + vz * cos_a;
let silver = from_type(&EntityType::SILVERFISH, center, &world, Uuid::new_v4());
let entity = silver.get_entity();
entity.set_pos(center);
entity.yaw.store(rand::random::<f32>() * 360.0);
entity.pitch.store(0.0);
entity.velocity.store(Vector3::new(
f64::from(rx),
f64::from(vy),
f64::from(rz),
));
world.spawn_entity(silver);
world.play_sound(Sound::EntitySilverfishHurt, SoundCategory::Hostile, &center);
}
}
})
} }
} }

View File

@ -9,9 +9,6 @@ pub mod weaving;
pub mod wind_charged; pub mod wind_charged;
pub mod wither; pub mod wither;
use std::future::Future;
use std::pin::Pin;
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_data::effect::StatusEffect; use pumpkin_data::effect::StatusEffect;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
@ -19,9 +16,7 @@ use pumpkin_nbt::tag::NbtTag;
use tracing::warn; use tracing::warn;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::{NBTInitFuture, NBTStorage, NBTStorageInit, NbtFuture}; use crate::entity::{NBTStorage, NBTStorageInit};
pub type EffectFuture<'a, T = ()> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait MobEffect: Send + Sync { pub trait MobEffect: Send + Sync {
/// Returns true if `apply_effect_tick` should be called for the current tick and duration. /// Returns true if `apply_effect_tick` should be called for the current tick and duration.
@ -30,34 +25,20 @@ pub trait MobEffect: Send + Sync {
} }
/// Applies periodic/tick-based effect logic on a living entity. /// Applies periodic/tick-based effect logic on a living entity.
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, _living: &LivingEntity, _amplifier: u8) {}
&'a self,
_living: &'a LivingEntity,
_amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {})
}
/// Called when an entity carrying this effect is hurt. /// Called when an entity carrying this effect is hurt.
fn on_mob_hurt<'a>( fn on_mob_hurt(
&'a self, &self,
_living: &'a LivingEntity, _living: &LivingEntity,
_amplifier: u8, _amplifier: u8,
_damage_type: &'a DamageType, _damage_type: &DamageType,
_damage_amount: f32, _damage_amount: f32,
) -> EffectFuture<'a, ()> { ) {
Box::pin(async move {})
} }
/// Called when an entity carrying this effect dies. /// Called when an entity carrying this effect dies.
fn on_mob_death<'a>( fn on_mob_death(&self, _living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {}
&'a self,
_living: &'a LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
) -> EffectFuture<'a, ()> {
Box::pin(async move {})
}
} }
pub static REGENERATION: regeneration::RegenerationMobEffect = regeneration::RegenerationMobEffect; pub static REGENERATION: regeneration::RegenerationMobEffect = regeneration::RegenerationMobEffect;
@ -99,57 +80,50 @@ pub fn get_mob_effect(effect: &'static StatusEffect) -> Option<&'static dyn MobE
} }
impl NBTStorage for pumpkin_data::potion::Effect { impl NBTStorage for pumpkin_data::potion::Effect {
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async { nbt.put("id", self.effect_type.minecraft_name);
nbt.put("id", self.effect_type.minecraft_name); if self.amplifier > 0 {
if self.amplifier > 0 { nbt.put("amplifier", NbtTag::Int(i32::from(self.amplifier)));
nbt.put("amplifier", NbtTag::Int(i32::from(self.amplifier))); }
} nbt.put("duration", NbtTag::Int(self.duration));
nbt.put("duration", NbtTag::Int(self.duration)); if self.ambient {
if self.ambient { nbt.put("ambient", NbtTag::Byte(1));
nbt.put("ambient", NbtTag::Byte(1)); }
} if !self.show_particles {
if !self.show_particles { nbt.put("show_particles", NbtTag::Byte(0));
nbt.put("show_particles", NbtTag::Byte(0)); }
} let show_icon: i8 = i8::from(self.show_icon);
let show_icon: i8 = i8::from(self.show_icon); nbt.put("show_icon", NbtTag::Byte(show_icon));
nbt.put("show_icon", NbtTag::Byte(show_icon));
})
} }
} }
impl NBTStorageInit for pumpkin_data::potion::Effect { impl NBTStorageInit for pumpkin_data::potion::Effect {
fn create_from_nbt<'a>(nbt: &'a mut NbtCompound) -> NBTInitFuture<'a, Self> fn create_from_nbt(nbt: &mut NbtCompound) -> Option<Self> {
where let Some(effect_id) = nbt.get_string("id") else {
Self: 'a, warn!("Unable to read effect. Effect id is not present");
{ return None;
Box::pin(async move { };
let Some(effect_id) = nbt.get_string("id") else { let Some(effect_type) = StatusEffect::from_minecraft_name(effect_id) else {
warn!("Unable to read effect. Effect id is not present"); warn!("Unable to read effect. Unknown effect type: {effect_id}");
return None; return None;
}; };
let Some(effect_type) = StatusEffect::from_minecraft_name(effect_id) else { let Some(show_icon) = nbt.get_byte("show_icon") else {
warn!("Unable to read effect. Unknown effect type: {effect_id}"); warn!("Unable to read effect. Show icon is not present");
return None; return None;
}; };
let Some(show_icon) = nbt.get_byte("show_icon") else { let amplifier = nbt.get_int("amplifier").unwrap_or(0) as u8;
warn!("Unable to read effect. Show icon is not present"); let duration = nbt.get_int("duration").unwrap_or(0);
return None; let ambient = nbt.get_byte("ambient").unwrap_or(0) == 1;
}; let show_particles = nbt.get_byte("show_particles").unwrap_or(1) == 1;
let amplifier = nbt.get_int("amplifier").unwrap_or(0) as u8; let show_icon = show_icon == 1;
let duration = nbt.get_int("duration").unwrap_or(0); Some(Self {
let ambient = nbt.get_byte("ambient").unwrap_or(0) == 1; effect_type,
let show_particles = nbt.get_byte("show_particles").unwrap_or(1) == 1; duration,
let show_icon = show_icon == 1; amplifier,
Some(Self { ambient,
effect_type, show_particles,
duration, show_icon,
amplifier, blend: false,
ambient,
show_particles,
show_icon,
blend: false,
})
}) })
} }
} }

View File

@ -4,7 +4,7 @@ use pumpkin_data::damage::DamageType;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::mob::slime::SlimeEntity; use crate::entity::mob::slime::SlimeEntity;
use crate::entity::r#type::from_type; use crate::entity::r#type::from_type;
@ -12,36 +12,29 @@ use crate::entity::r#type::from_type;
pub struct OozingMobEffect; pub struct OozingMobEffect;
impl MobEffect for OozingMobEffect { impl MobEffect for OozingMobEffect {
fn on_mob_death<'a>( fn on_mob_death(&self, living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {
&'a self, // Slimes are immune
living: &'a LivingEntity, if living.entity.entity_type == &EntityType::SLIME {
_amplifier: u8, return;
_damage_type: &'a DamageType, }
) -> EffectFuture<'a, ()> {
Box::pin(async move { let world = living.entity.world.load();
// Slimes are immune let pos = living.entity.pos.load();
if living.entity.entity_type == &EntityType::SLIME { let spawn_pos = Vector3::new(pos.x, pos.y + 0.5, pos.z);
return;
// Spawns 2 slimes of size 2 (medium slimes)
for _ in 0..2 {
let entity_arc = from_type(&EntityType::SLIME, spawn_pos, &world, Uuid::new_v4());
let entity = entity_arc.get_entity();
entity.set_pos(spawn_pos);
entity.yaw.store(rand::random::<f32>() * 360.0);
entity.pitch.store(0.0);
if let Some(slime) = entity_arc.cast_any().downcast_ref::<SlimeEntity>() {
slime.set_size(2, true);
} }
let world = living.entity.world.load(); world.spawn_entity(entity_arc);
let pos = living.entity.pos.load(); }
let spawn_pos = Vector3::new(pos.x, pos.y + 0.5, pos.z);
// Spawns 2 slimes of size 2 (medium slimes)
for _ in 0..2 {
let entity_arc = from_type(&EntityType::SLIME, spawn_pos, &world, Uuid::new_v4());
let entity = entity_arc.get_entity();
entity.set_pos(spawn_pos);
entity.yaw.store(rand::random::<f32>() * 360.0);
entity.pitch.store(0.0);
if let Some(slime) = entity_arc.cast_any().downcast_ref::<SlimeEntity>() {
slime.set_size(2, true);
}
world.spawn_entity(entity_arc);
}
})
} }
} }

View File

@ -1,6 +1,6 @@
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct PoisonMobEffect; pub struct PoisonMobEffect;
@ -18,25 +18,19 @@ impl MobEffect for PoisonMobEffect {
} }
} }
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
&'a self, let current_health = living.health.load();
living: &'a LivingEntity, if current_health > 1.0
_amplifier: u8, && let Some(dyn_self) = living
) -> EffectFuture<'a, ()> { .entity
Box::pin(async move { .world
let current_health = living.health.load(); .load()
if current_health > 1.0 .get_entity_by_id(living.entity.entity_id)
&& let Some(dyn_self) = living {
.entity let damage_amount = (current_health - 1.0).min(1.0);
.world if damage_amount > 0.0 {
.load() dyn_self.damage(&*dyn_self, damage_amount, DamageType::MAGIC);
.get_entity_by_id(living.entity.entity_id)
{
let damage_amount = (current_health - 1.0).min(1.0);
if damage_amount > 0.0 {
dyn_self.damage(&*dyn_self, damage_amount, DamageType::MAGIC);
}
} }
}) }
} }
} }

View File

@ -1,4 +1,4 @@
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct RaidOmenMobEffect; pub struct RaidOmenMobEffect;
@ -8,27 +8,21 @@ impl MobEffect for RaidOmenMobEffect {
duration == 1 duration == 1
} }
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
&'a self, let world = living.entity.world.load();
living: &'a LivingEntity, if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
_amplifier: u8, && let Some(player) = entity.get_player()
) -> EffectFuture<'a, ()> { && !player.is_spectator()
Box::pin(async move { {
let world = living.entity.world.load(); let raid_pos = player
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id) .get_raid_omen_position()
&& let Some(player) = entity.get_player() .unwrap_or_else(|| living.entity.block_pos.load());
&& !player.is_spectator() let mut raids = world
{ .raids
let raid_pos = player .lock()
.get_raid_omen_position() .unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(|| living.entity.block_pos.load()); raids.create_or_extend_raid(player, raid_pos, &world);
let mut raids = world player.clear_raid_omen_position();
.raids }
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
raids.create_or_extend_raid(player, raid_pos, &world);
player.clear_raid_omen_position();
}
})
} }
} }

View File

@ -1,4 +1,4 @@
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct RegenerationMobEffect; pub struct RegenerationMobEffect;
@ -16,17 +16,11 @@ impl MobEffect for RegenerationMobEffect {
} }
} }
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
&'a self, let current_health = living.health.load();
living: &'a LivingEntity, let max_health = living.get_max_health();
_amplifier: u8, if current_health < max_health && current_health > 0.0 {
) -> EffectFuture<'a, ()> { living.heal(1.0);
Box::pin(async move { }
let current_health = living.health.load();
let max_health = living.get_max_health();
if current_health < max_health && current_health > 0.0 {
living.heal(1.0);
}
})
} }
} }

View File

@ -1,4 +1,4 @@
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct SaturationMobEffect; pub struct SaturationMobEffect;
@ -8,22 +8,16 @@ impl MobEffect for SaturationMobEffect {
true true
} }
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, living: &LivingEntity, amplifier: u8) {
&'a self, let world = living.entity.world.load();
living: &'a LivingEntity, if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
amplifier: u8, && let Some(player) = entity.get_player()
) -> EffectFuture<'a, ()> { {
Box::pin(async move { let hunger = amplifier + 1;
let world = living.entity.world.load(); player.hunger_manager.add_hunger(hunger);
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id) player
&& let Some(player) = entity.get_player() .hunger_manager
{ .add_saturation(f32::from(hunger) * 2.0);
let hunger = amplifier + 1; }
player.hunger_manager.add_hunger(hunger);
player
.hunger_manager
.add_saturation(f32::from(hunger) * 2.0);
}
})
} }
} }

View File

@ -7,61 +7,54 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct WeavingMobEffect; pub struct WeavingMobEffect;
impl MobEffect for WeavingMobEffect { impl MobEffect for WeavingMobEffect {
fn on_mob_death<'a>( fn on_mob_death(&self, living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {
&'a self, let world = living.entity.world.load();
living: &'a LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let world = living.entity.world.load();
// Check if mob griefing is enabled or if player // Check if mob griefing is enabled or if player
let mob_griefing = world.level_info.load().game_rules.mob_griefing; let mob_griefing = world.level_info.load().game_rules.mob_griefing;
if !living.is_player() && !mob_griefing { if !living.is_player() && !mob_griefing {
return; return;
}
let center_pos = living.entity.block_pos.load();
let cobweb_count = (rand::random::<u32>() % 2 + 2) as usize; // 2 to 3 cobwebs
let mut positions_to_transform = HashSet::new();
// Sample up to 15 random positions in a cube of radius 1
for _ in 0..15 {
let dx = (rand::random::<u32>() % 3) as i32 - 1;
let dy = (rand::random::<u32>() % 3) as i32 - 1;
let dz = (rand::random::<u32>() % 3) as i32 - 1;
let target_pos = BlockPos(center_pos.0 + Vector3::new(dx, dy, dz));
let below_pos = BlockPos(target_pos.0 + Vector3::new(0, -1, 0));
let target_state = world.get_block_state(&target_pos);
let below_state = world.get_block_state(&below_pos);
if target_state.is_air()
&& !below_state.is_air()
&& positions_to_transform.insert(target_pos)
&& positions_to_transform.len() >= cobweb_count
{
break;
} }
}
let center_pos = living.entity.block_pos.load(); for target_pos in positions_to_transform {
let cobweb_count = (rand::random::<u32>() % 2 + 2) as usize; // 2 to 3 cobwebs world.set_block_state(
&target_pos,
let mut positions_to_transform = HashSet::new(); Block::COBWEB.default_state.id,
BlockFlags::NOTIFY_ALL,
// Sample up to 15 random positions in a cube of radius 1 );
for _ in 0..15 { world.sync_world_event(WorldEvent::AnimationSpawnCobweb, target_pos, 0);
let dx = (rand::random::<u32>() % 3) as i32 - 1; }
let dy = (rand::random::<u32>() % 3) as i32 - 1;
let dz = (rand::random::<u32>() % 3) as i32 - 1;
let target_pos = BlockPos(center_pos.0 + Vector3::new(dx, dy, dz));
let below_pos = BlockPos(target_pos.0 + Vector3::new(0, -1, 0));
let target_state = world.get_block_state(&target_pos);
let below_state = world.get_block_state(&below_pos);
if target_state.is_air()
&& !below_state.is_air()
&& positions_to_transform.insert(target_pos)
&& positions_to_transform.len() >= cobweb_count
{
break;
}
}
for target_pos in positions_to_transform {
world.set_block_state(
&target_pos,
Block::COBWEB.default_state.id,
BlockFlags::NOTIFY_ALL,
);
world.sync_world_event(WorldEvent::AnimationSpawnCobweb, target_pos, 0);
}
})
} }
} }

View File

@ -2,7 +2,7 @@ use pumpkin_data::damage::DamageType;
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::projectile::wind_charge::BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR; use crate::entity::projectile::wind_charge::BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR;
use crate::world::explosion::ExplosionInteraction; use crate::world::explosion::ExplosionInteraction;
@ -10,35 +10,26 @@ use crate::world::explosion::ExplosionInteraction;
pub struct WindChargedMobEffect; pub struct WindChargedMobEffect;
impl MobEffect for WindChargedMobEffect { impl MobEffect for WindChargedMobEffect {
fn on_mob_death<'a>( fn on_mob_death(&self, living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {
&'a self, let world = living.entity.world.load();
living: &'a LivingEntity, let pos = living.entity.pos.load();
_amplifier: u8, let height = living.entity.height();
_damage_type: &'a DamageType, let center = Vector3::new(pos.x, pos.y + f64::from(height) / 2.0, pos.z);
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let world = living.entity.world.load();
let pos = living.entity.pos.load();
let height = living.entity.height();
let center = Vector3::new(pos.x, pos.y + f64::from(height) / 2.0, pos.z);
// gustStrength = 3.0 + random * 2.0 // gustStrength = 3.0 + random * 2.0
let gust_strength = 3.0 + rand::random::<f32>() * 2.0; let gust_strength = 3.0 + rand::random::<f32>() * 2.0;
world world.explode_with_calculator(
.explode_with_calculator( center,
center, gust_strength,
gust_strength, ExplosionInteraction::Trigger,
ExplosionInteraction::Trigger, Some(BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR.clone()),
Some(BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR.clone()), );
)
.await;
world.play_sound( world.play_sound(
Sound::EntityBreezeWindBurst, Sound::EntityBreezeWindBurst,
SoundCategory::Neutral, SoundCategory::Hostile,
&center, &center,
); );
})
} }
} }

View File

@ -1,6 +1,6 @@
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use crate::entity::effect::{EffectFuture, MobEffect}; use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
pub struct WitherMobEffect; pub struct WitherMobEffect;
@ -18,20 +18,14 @@ impl MobEffect for WitherMobEffect {
} }
} }
fn apply_effect_tick<'a>( fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
&'a self, let dyn_self = living
living: &'a LivingEntity, .entity
_amplifier: u8, .world
) -> EffectFuture<'a, ()> { .load()
Box::pin(async move { .get_entity_by_id(living.entity.entity_id);
let dyn_self = living if let Some(dyn_self) = dyn_self {
.entity dyn_self.damage(&*dyn_self, 1.0, DamageType::WITHER);
.world }
.load()
.get_entity_by_id(living.entity.entity_id);
if let Some(dyn_self) = dyn_self {
dyn_self.damage(&*dyn_self, 1.0, DamageType::WITHER);
}
})
} }
} }

View File

@ -115,14 +115,11 @@ impl EntityBase for ExperienceOrbEntity {
if can_pickup { if can_pickup {
player.living_entity.pickup(&self.entity, 1); player.living_entity.pickup(&self.entity, 1);
self.entity.remove(); self.entity.remove();
let player_clone = player.clone();
let amount = self.amount as i32; let amount = self.amount as i32;
tokio::spawn(async move { let remaining = player.apply_mending_from_xp(amount);
let remaining = player_clone.apply_mending_from_xp(amount).await; if remaining > 0 {
if remaining > 0 { player.add_experience_points(remaining);
player_clone.add_experience_points(remaining).await; }
}
});
} }
} }
} }

View File

@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use super::{NBTStorage, NBTStorageInit, player::Player}; use super::{NBTStorage, NBTStorageInit, player::Player};
use crate::entity::NbtFuture;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
use pumpkin_data::damage::DamageType; use pumpkin_data::damage::DamageType;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
@ -176,26 +175,22 @@ impl HungerManager {
} }
impl NBTStorage for HungerManager { impl NBTStorage for HungerManager {
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async { nbt.put_int("foodLevel", self.level.load().into());
nbt.put_int("foodLevel", self.level.load().into()); nbt.put_float("foodSaturationLevel", self.saturation.load());
nbt.put_float("foodSaturationLevel", self.saturation.load()); nbt.put_float("foodExhaustionLevel", self.exhaustion.load());
nbt.put_float("foodExhaustionLevel", self.exhaustion.load()); nbt.put_int("foodTickTimer", self.tick_timer.load() as i32);
nbt.put_int("foodTickTimer", self.tick_timer.load() as i32);
})
} }
fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_nbt_non_mut(&self, nbt: &NbtCompound) {
Box::pin(async move { self.level
self.level .store(nbt.get_int("foodLevel").unwrap_or(20) as u8);
.store(nbt.get_int("foodLevel").unwrap_or(20) as u8); self.saturation
self.saturation .store(nbt.get_float("foodSaturationLevel").unwrap_or(5.0));
.store(nbt.get_float("foodSaturationLevel").unwrap_or(5.0)); self.exhaustion
self.exhaustion .store(nbt.get_float("foodExhaustionLevel").unwrap_or(0.0));
.store(nbt.get_float("foodExhaustionLevel").unwrap_or(0.0)); self.tick_timer
self.tick_timer .store(nbt.get_int("foodTickTimer").unwrap_or(0) as u32);
.store(nbt.get_int("foodTickTimer").unwrap_or(0) as u32);
})
} }
} }

View File

@ -13,9 +13,7 @@ use pumpkin_util::math::{
}; };
use crate::{ use crate::{
entity::{ entity::{Entity, EntityBase, living::LivingEntity, player::Player},
Entity, EntityBase, EntityBaseFuture, NbtFuture, living::LivingEntity, player::Player,
},
server::Server, server::Server,
}; };
@ -161,85 +159,81 @@ impl InteractionEntity {
} }
impl EntityBase for InteractionEntity { impl EntityBase for InteractionEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { nbt.put_float(
nbt.put_float( "width",
"width",
*self
.width
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
nbt.put_float(
"height",
*self
.height
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
nbt.put_bool("response", self.response.load(Ordering::Relaxed));
let attack = *self
.attack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(attack) = attack {
nbt.put("attack", NbtTag::Compound(attack.to_nbt()));
}
let interaction = *self
.interaction
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(interaction) = interaction {
nbt.put("interaction", NbtTag::Compound(interaction.to_nbt()));
}
})
}
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
let width = nbt.get_float("width").unwrap_or(1.0);
let height = nbt.get_float("height").unwrap_or(1.0);
let response = nbt.get_bool("response").unwrap_or(false);
*self *self
.width .width
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = width; .unwrap_or_else(std::sync::PoisonError::into_inner),
);
nbt.put_float(
"height",
*self *self
.height .height
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = height; .unwrap_or_else(std::sync::PoisonError::into_inner),
self.response.store(response, Ordering::Relaxed); );
self.update_dimensions(); nbt.put_bool("response", self.response.load(Ordering::Relaxed));
if let Some(attack_compound) = nbt.get_compound("attack") { let attack = *self
*self .attack
.attack .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(std::sync::PoisonError::into_inner) = if let Some(attack) = attack {
PlayerAction::from_nbt(attack_compound); nbt.put("attack", NbtTag::Compound(attack.to_nbt()));
} else { }
*self
.attack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
if let Some(interaction_compound) = nbt.get_compound("interaction") { let interaction = *self
*self .interaction
.interaction .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(std::sync::PoisonError::into_inner) = if let Some(interaction) = interaction {
PlayerAction::from_nbt(interaction_compound); nbt.put("interaction", NbtTag::Compound(interaction.to_nbt()));
} else { }
*self }
.interaction
.lock() fn read_custom_nbt(&self, nbt: &NbtCompound) {
.unwrap_or_else(std::sync::PoisonError::into_inner) = None; let width = nbt.get_float("width").unwrap_or(1.0);
} let height = nbt.get_float("height").unwrap_or(1.0);
}) let response = nbt.get_bool("response").unwrap_or(false);
*self
.width
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = width;
*self
.height
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = height;
self.response.store(response, Ordering::Relaxed);
self.update_dimensions();
if let Some(attack_compound) = nbt.get_compound("attack") {
*self
.attack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
PlayerAction::from_nbt(attack_compound);
} else {
*self
.attack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
if let Some(interaction_compound) = nbt.get_compound("interaction") {
*self
.interaction
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
PlayerAction::from_nbt(interaction_compound);
} else {
*self
.interaction
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
} }
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {} fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {}
@ -331,21 +325,15 @@ impl EntityBase for InteractionEntity {
false false
} }
fn interact<'a>( fn interact(&self, player: &Arc<Player>, _item_stack: &mut ItemStack) -> bool {
&'a self, let timestamp = self.entity.world.load().get_world_age();
player: &'a Arc<Player>, *self
_item_stack: &'a mut ItemStack, .interaction
) -> EntityBaseFuture<'a, bool> { .lock()
Box::pin(async move { .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(PlayerAction {
let timestamp = self.entity.world.load().get_world_age(); player: player.gameprofile.id,
*self timestamp,
.interaction });
.lock() true
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(PlayerAction {
player: player.gameprofile.id,
timestamp,
});
true
})
} }
} }

View File

@ -25,7 +25,7 @@ use std::sync::{
}, },
}; };
use super::{Entity, EntityBase, NbtFuture, living::LivingEntity, player::Player}; use super::{Entity, EntityBase, living::LivingEntity, player::Player};
pub struct ItemEntity { pub struct ItemEntity {
entity: Entity, entity: Entity,
@ -381,23 +381,18 @@ impl ItemEntity {
if age >= 6000 { if age >= 6000 {
let entity_id = entity.entity_id; let entity_id = entity.entity_id;
let world = entity.world.load_full(); let world = entity.world.load_full();
tokio::spawn(async move { let mut despawn_event =
let mut despawn_event = crate::plugin::api::events::entity::item_despawn::ItemDespawnEvent::new(entity_id);
crate::plugin::api::events::entity::item_despawn::ItemDespawnEvent::new( if let Some(server) = world.server.upgrade() {
entity_id, server
); .plugin_manager
if let Some(server) = world.server.upgrade() { .fire_blocking(&server, &mut despawn_event);
server }
.plugin_manager if !despawn_event.cancelled
.fire(&server, &mut despawn_event) && let Some(e) = world.get_entity_by_id(entity_id)
.await; {
} e.get_entity().remove();
if !despawn_event.cancelled }
&& let Some(e) = world.get_entity_by_id(entity_id)
{
e.get_entity().remove();
}
});
return false; return false;
} }
@ -608,51 +603,47 @@ impl EntityBase for ItemEntity {
0.04 0.04
} }
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { let item = self
let item = self .item_stack
.item_stack .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(std::sync::PoisonError::into_inner); let mut item_compound = NbtCompound::new();
let mut item_compound = NbtCompound::new(); item.write_item_stack(&mut item_compound);
item.write_item_stack(&mut item_compound); nbt.put_compound("Item", item_compound);
nbt.put_compound("Item", item_compound);
nbt.put_short("Age", self.item_age.load(Ordering::Relaxed) as i16); nbt.put_short("Age", self.item_age.load(Ordering::Relaxed) as i16);
nbt.put_short( nbt.put_short(
"PickupDelay", "PickupDelay",
self.pickup_delay.load(Ordering::Relaxed) as i16, self.pickup_delay.load(Ordering::Relaxed) as i16,
); );
nbt.put_short("Health", self.health.load(Relaxed) as i16); nbt.put_short("Health", self.health.load(Relaxed) as i16);
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async { // Restore the item stack from the "Item" compound
// Restore the item stack from the "Item" compound if let Some(item_compound) = nbt.get_compound("Item")
if let Some(item_compound) = nbt.get_compound("Item") && let Some(stack) = ItemStack::read_item_stack(item_compound)
&& let Some(stack) = ItemStack::read_item_stack(item_compound) {
{ *self
*self .item_stack
.item_stack .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack; }
}
// Vanilla stores Age as a short // Vanilla stores Age as a short
self.item_age self.item_age
.store(nbt.get_short("Age").unwrap_or(0) as u32, Ordering::Relaxed); .store(nbt.get_short("Age").unwrap_or(0) as u32, Ordering::Relaxed);
// Vanilla stores PickupDelay as a short // Vanilla stores PickupDelay as a short
if let Some(delay) = nbt.get_short("PickupDelay") { if let Some(delay) = nbt.get_short("PickupDelay") {
self.pickup_delay.store(delay as u8, Ordering::Relaxed); self.pickup_delay.store(delay as u8, Ordering::Relaxed);
} }
// Vanilla stores Health as a short // Vanilla stores Health as a short
if let Some(health) = nbt.get_short("Health") { if let Some(health) = nbt.get_short("Health") {
self.health.store(health as f32, Relaxed); self.health.store(health as f32, Relaxed);
} }
})
} }
fn cast_any(&self) -> &dyn std::any::Any { fn cast_any(&self) -> &dyn std::any::Any {

View File

@ -192,7 +192,7 @@ impl LightningBoltEntity {
} }
impl EntityBase for LightningBoltEntity { impl EntityBase for LightningBoltEntity {
fn tick(&self, caller: &Arc<dyn EntityBase>, _server: &Server) { fn tick(&self, _caller: &Arc<dyn EntityBase>, _server: &Server) {
let entity = &self.entity; let entity = &self.entity;
let life = self.life.load(Ordering::Relaxed); let life = self.life.load(Ordering::Relaxed);
@ -261,14 +261,7 @@ impl EntityBase for LightningBoltEntity {
} }
let hit_id = hit_entity.get_entity().entity_id; let hit_id = hit_entity.get_entity().entity_id;
if hit_guard.insert(hit_id) { if hit_guard.insert(hit_id) {
let caller_clone = caller.clone(); hit_entity.on_lightning_strike(hit_entity.as_ref(), self);
let target = hit_entity.clone();
tokio::spawn(async move {
if let Some(lightning) = caller_clone.cast_any().downcast_ref::<Self>()
{
target.on_lightning_strike(target.as_ref(), lightning).await;
}
});
} }
} }
} }

View File

@ -24,6 +24,7 @@ use tracing::warn;
use super::experience_orb::ExperienceOrbEntity; use super::experience_orb::ExperienceOrbEntity;
use super::{Entity, EntityBase, NBTStorageInit}; use super::{Entity, EntityBase, NBTStorageInit};
use crate::block::OnLandedUponArgs; use crate::block::OnLandedUponArgs;
use crate::entity::NBTStorage;
use crate::entity::attributes::AttributeInstance; use crate::entity::attributes::AttributeInstance;
use crate::entity::attributes::Modifier; use crate::entity::attributes::Modifier;
use crate::entity::attributes::ModifierOperation; use crate::entity::attributes::ModifierOperation;
@ -31,7 +32,6 @@ use crate::entity::combat::knockback_after_resistance;
use crate::entity::mob::equipment::DEFAULT_EQUIPMENT_DROP_CHANCE; use crate::entity::mob::equipment::DEFAULT_EQUIPMENT_DROP_CHANCE;
use crate::entity::mob::slime::SlimeEntity; use crate::entity::mob::slime::SlimeEntity;
use crate::entity::player::statistics::{CustomStatistic, StatisticCategory}; use crate::entity::player::statistics::{CustomStatistic, StatisticCategory};
use crate::entity::{NBTStorage, NbtFuture};
use crate::server::Server; use crate::server::Server;
use crate::world::loot::{LootContextParameters, LootTableExt}; use crate::world::loot::{LootContextParameters, LootTableExt};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
@ -1443,9 +1443,13 @@ impl LivingEntity {
fall_distance: f32, fall_distance: f32,
damage_per_distance: f32, damage_per_distance: f32,
) { ) {
let may_fly = caller let may_fly = caller.get_player().is_some_and(|player| {
.get_player() player
.is_some_and(|player| player.abilities.blocking_lock().allow_flying); .abilities
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.allow_flying
});
if may_fly || self.is_immune_to_fall_damage() { if may_fly || self.is_immune_to_fall_damage() {
return; return;
} }
@ -1472,7 +1476,7 @@ impl LivingEntity {
} }
} }
pub async fn get_death_message( pub fn get_death_message(
dyn_self: &dyn EntityBase, dyn_self: &dyn EntityBase,
damage_type: DamageType, damage_type: DamageType,
source: Option<&dyn EntityBase>, source: Option<&dyn EntityBase>,
@ -1486,16 +1490,13 @@ impl LivingEntity {
TextComponent::translate_cross( TextComponent::translate_cross(
format!("death.attack.{}.player", damage_type.message_id), format!("death.attack.{}.player", damage_type.message_id),
format!("death.attack.{}.player", damage_type.message_id), format!("death.attack.{}.player", damage_type.message_id),
[ [dyn_self.get_display_name(), cause.get_display_name()],
dyn_self.get_display_name().await,
cause.get_display_name().await,
],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(
format!("death.attack.{}", damage_type.message_id), format!("death.attack.{}", damage_type.message_id),
format!("death.attack.{}", damage_type.message_id), format!("death.attack.{}", damage_type.message_id),
[dyn_self.get_display_name().await], [dyn_self.get_display_name()],
) )
} }
} }
@ -1504,14 +1505,14 @@ impl LivingEntity {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::DEATH_FELL_ACCIDENT_GENERIC, translation::java::DEATH_FELL_ACCIDENT_GENERIC,
translation::bedrock::DEATH_FELL_ACCIDENT_GENERIC, translation::bedrock::DEATH_FELL_ACCIDENT_GENERIC,
[dyn_self.get_display_name().await], [dyn_self.get_display_name()],
) )
} }
DeathMessageType::IntentionalGameDesign => TextComponent::text("[") DeathMessageType::IntentionalGameDesign => TextComponent::text("[")
.add_child(TextComponent::translate_cross( .add_child(TextComponent::translate_cross(
format!("death.attack.{}.message", damage_type.message_id), format!("death.attack.{}.message", damage_type.message_id),
format!("death.attack.{}.message", damage_type.message_id), format!("death.attack.{}.message", damage_type.message_id),
[dyn_self.get_display_name().await], [dyn_self.get_display_name()],
)) ))
.add_child(TextComponent::text("]")), .add_child(TextComponent::text("]")),
} }
@ -1618,7 +1619,7 @@ impl LivingEntity {
}; };
for (effect_type, amplifier) in active_effects_vec { for (effect_type, amplifier) in active_effects_vec {
if let Some(mob_effect) = crate::entity::effect::get_mob_effect(effect_type) { if let Some(mob_effect) = crate::entity::effect::get_mob_effect(effect_type) {
mob_effect.on_mob_death(self, amplifier, &damage_type).await; mob_effect.on_mob_death(self, amplifier, &damage_type);
} }
} }
@ -1687,7 +1688,7 @@ impl LivingEntity {
let show_death_messages = { world.level_info.load().game_rules.show_death_messages }; let show_death_messages = { world.level_info.load().game_rules.show_death_messages };
if self.entity.entity_type == &EntityType::PLAYER && show_death_messages { if self.entity.entity_type == &EntityType::PLAYER && show_death_messages {
//TODO: KillCredit //TODO: KillCredit
let death_message = Self::get_death_message(dyn_self, damage_type, source, cause).await; let death_message = Self::get_death_message(dyn_self, damage_type, source, cause);
if let Some(server) = world.server.upgrade() { if let Some(server) = world.server.upgrade() {
for player in server.get_all_players() { for player in server.get_all_players() {
player.send_system_message(&death_message).await; player.send_system_message(&death_message).await;
@ -1816,15 +1817,7 @@ impl LivingEntity {
} }
for (mob_effect, amplifier) in effects_to_apply { for (mob_effect, amplifier) in effects_to_apply {
let entity_id = self.entity.entity_id; mob_effect.apply_effect_tick(self, amplifier);
let world = self.entity.world.load_full();
tokio::spawn(async move {
if let Some(entity) = world.get_entity_by_id(entity_id)
&& let Some(living) = entity.get_living_entity()
{
mob_effect.apply_effect_tick(living, amplifier).await;
}
});
} }
} }
@ -2093,103 +2086,97 @@ impl LivingEntity {
} }
impl LivingEntity { impl LivingEntity {
pub fn write_living_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { pub fn write_living_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { nbt.put("Health", NbtTag::Float(self.health.load()));
nbt.put("Health", NbtTag::Float(self.health.load())); // Avoid persisting a lethal fall distance when the entity is dead to prevent death loops
// Avoid persisting a lethal fall distance when the entity is dead to prevent death loops let fall_distance = if self.dead.load(Relaxed) {
let fall_distance = if self.dead.load(Relaxed) { 0.0
0.0 } else {
} else { self.fall_distance.load()
self.fall_distance.load() };
// Persist current absorption amount
nbt.put("AbsorptionAmount", NbtTag::Float(self.absorption.load()));
nbt.put("FallDistance", NbtTag::Float(fall_distance));
nbt.put_short("HurtTime", self.hurt_cooldown.load(Relaxed).max(0) as i16);
nbt.put_short("DeathTime", i16::from(self.death_time.load(Relaxed)));
nbt.put_bool("FallFlying", self.entity.is_fall_flying());
{
let effects_vec: Vec<pumpkin_data::potion::Effect> = {
let effects = self
.active_effects
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
effects.values().cloned().collect()
}; };
// Persist current absorption amount if !effects_vec.is_empty() {
nbt.put("AbsorptionAmount", NbtTag::Float(self.absorption.load())); // Iterate effects and create Box<[NbtTag]>
nbt.put("FallDistance", NbtTag::Float(fall_distance)); let mut effects_list = Vec::with_capacity(effects_vec.len());
nbt.put_short("HurtTime", self.hurt_cooldown.load(Relaxed).max(0) as i16); for effect in effects_vec {
nbt.put_short("DeathTime", i16::from(self.death_time.load(Relaxed))); let mut effect_nbt = pumpkin_nbt::compound::NbtCompound::new();
nbt.put_bool("FallFlying", self.entity.is_fall_flying()); effect.write_nbt(&mut effect_nbt);
{ effects_list.push(NbtTag::Compound(effect_nbt));
let effects_vec: Vec<pumpkin_data::potion::Effect> = { }
let effects = self nbt.put("active_effects", NbtTag::List(effects_list));
}
}
//TODO: write equipment
// todo more...
}
pub fn read_living_nbt_non_mut(&self, nbt: &NbtCompound) {
self.health.store(nbt.get_float("Health").unwrap_or(20.0));
// Clamp any persisted absorption to the entity's configured max
let raw_abs = nbt.get_float("AbsorptionAmount").unwrap_or(0.0);
let max_abs = self.get_attribute_value(&Attributes::MAX_ABSORPTION) as f32;
let clamped_abs = raw_abs.max(0.0).min(max_abs);
self.absorption.store(clamped_abs);
// Load fall distance, but if this entity is currently marked dead ensure we don't restore
// a lethal fall distance that would immediately re-kill on spawn.
let fd = nbt
.get_float("FallDistance")
.or_else(|| nbt.get_float("fall_distance"))
.unwrap_or(0.0);
if self.dead.load(Relaxed) {
self.fall_distance.store(0.0);
} else {
self.fall_distance.store(fd);
}
if let Some(hurt_time) = nbt.get_short("HurtTime") {
self.hurt_cooldown.store(i32::from(hurt_time), Relaxed);
}
if let Some(death_time) = nbt.get_short("DeathTime") {
self.death_time.store(death_time as u8, Relaxed);
}
self.entity
.fall_flying
.store(nbt.get_bool("FallFlying").unwrap_or(false), Relaxed);
{
let nbt_effects = nbt.get_list("active_effects");
if let Some(nbt_effects) = nbt_effects {
let mut read_effects = Vec::new();
for effect in nbt_effects {
if let NbtTag::Compound(effect_nbt) = effect {
if let Some(mut effect) = Effect::create_from_nbt(&mut effect_nbt.clone()) {
effect.blend = true; // TODO: change, is taken from effect give command
read_effects.push(effect);
} else {
warn!("Unable to read effect from nbt");
}
}
}
if !read_effects.is_empty() {
let mut active_effects = self
.active_effects .active_effects
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
effects.values().cloned().collect() for effect in read_effects {
}; active_effects.insert(effect.effect_type, effect);
if !effects_vec.is_empty() {
// Iterate effects and create Box<[NbtTag]>
let mut effects_list = Vec::with_capacity(effects_vec.len());
for effect in effects_vec {
let mut effect_nbt = pumpkin_nbt::compound::NbtCompound::new();
effect.write_nbt(&mut effect_nbt).await;
effects_list.push(NbtTag::Compound(effect_nbt));
}
nbt.put("active_effects", NbtTag::List(effects_list));
}
}
//TODO: write equipment
// todo more...
})
}
pub fn read_living_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
self.health.store(nbt.get_float("Health").unwrap_or(20.0));
// Clamp any persisted absorption to the entity's configured max
let raw_abs = nbt.get_float("AbsorptionAmount").unwrap_or(0.0);
let max_abs = self.get_attribute_value(&Attributes::MAX_ABSORPTION) as f32;
let clamped_abs = raw_abs.max(0.0).min(max_abs);
self.absorption.store(clamped_abs);
// Load fall distance, but if this entity is currently marked dead ensure we don't restore
// a lethal fall distance that would immediately re-kill on spawn.
let fd = nbt
.get_float("FallDistance")
.or_else(|| nbt.get_float("fall_distance"))
.unwrap_or(0.0);
if self.dead.load(Relaxed) {
self.fall_distance.store(0.0);
} else {
self.fall_distance.store(fd);
}
if let Some(hurt_time) = nbt.get_short("HurtTime") {
self.hurt_cooldown.store(i32::from(hurt_time), Relaxed);
}
if let Some(death_time) = nbt.get_short("DeathTime") {
self.death_time.store(death_time as u8, Relaxed);
}
self.entity
.fall_flying
.store(nbt.get_bool("FallFlying").unwrap_or(false), Relaxed);
{
let nbt_effects = nbt.get_list("active_effects");
if let Some(nbt_effects) = nbt_effects {
let mut read_effects = Vec::new();
for effect in nbt_effects {
if let NbtTag::Compound(effect_nbt) = effect {
if let Some(mut effect) =
Effect::create_from_nbt(&mut effect_nbt.clone()).await
{
effect.blend = true; // TODO: change, is taken from effect give command
read_effects.push(effect);
} else {
warn!("Unable to read effect from nbt");
}
}
}
if !read_effects.is_empty() {
let mut active_effects = self
.active_effects
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for effect in read_effects {
active_effects.insert(effect.effect_type, effect);
}
} }
} }
} }
}) }
// todo more... // todo more...
} }
@ -2828,9 +2815,7 @@ impl EntityBase for LivingEntity {
.cooldown_group .cooldown_group
.clone() .clone()
.unwrap_or_else(|| item.item.registry_key.to_string()); .unwrap_or_else(|| item.item.registry_key.to_string());
player player.start_cooldown(group, (cooldown.seconds * 20.0) as i32);
.start_cooldown(group, (cooldown.seconds * 20.0) as i32)
.await;
} }
} }
@ -2932,7 +2917,12 @@ impl LivingEntity {
} }
ConsumeEffect::TeleportRandomly(diameter) => { ConsumeEffect::TeleportRandomly(diameter) => {
// Java Edition dismounts the consumer before random teleport attempts. // Java Edition dismounts the consumer before random teleport attempts.
let vehicle = caller.get_entity().vehicle.lock().await.clone(); let vehicle = caller
.get_entity()
.vehicle
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
if let Some(vehicle) = vehicle { if let Some(vehicle) = vehicle {
vehicle vehicle
.get_entity() .get_entity()
@ -2959,7 +2949,7 @@ impl LivingEntity {
self.fall_distance.store(0.0); self.fall_distance.store(0.0);
// Vanilla broadcasts entity event 46 (teleport particles) on success. // Vanilla broadcasts entity event 46 (teleport particles) on success.
world.send_entity_status(&self.entity, EntityStatus::Teleport, None); world.send_entity_status(&self.entity, EntityStatus::Teleport, None);
world.emit_game_event("teleport", center).await; world.emit_game_event("teleport", center);
world.play_sound( world.play_sound(
Sound::ItemChorusFruitTeleport, Sound::ItemChorusFruitTeleport,
SoundCategory::Players, SoundCategory::Players,

View File

@ -1,8 +1,7 @@
use std::sync::{Arc, atomic::Ordering}; use std::sync::{Arc, Mutex, atomic::Ordering};
use tokio::sync::Mutex;
use crate::{ use crate::{
entity::{Entity, EntityBase, EntityBaseFuture, NbtFuture, living::LivingEntity}, entity::{Entity, EntityBase, EntityBaseFuture, living::LivingEntity},
net::{bedrock::BedrockClient, java::JavaClient}, net::{bedrock::BedrockClient, java::JavaClient},
server::Server, server::Server,
}; };
@ -26,21 +25,23 @@ impl MarkerEntity {
} }
impl EntityBase for MarkerEntity { impl EntityBase for MarkerEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { let data = self
let data = self.data.lock().await; .data
if !data.is_empty() { .lock()
nbt.put("data", NbtTag::Compound(data.clone())); .unwrap_or_else(std::sync::PoisonError::into_inner);
} if !data.is_empty() {
}) nbt.put("data", NbtTag::Compound(data.clone()));
}
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(data) = nbt.get_compound("data") {
if let Some(data) = nbt.get_compound("data") { *self
*self.data.lock().await = data.clone(); .data
} .lock()
}) .unwrap_or_else(std::sync::PoisonError::into_inner) = data.clone();
}
} }
fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {} fn tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>, _server: &'a Server) {}

View File

@ -14,7 +14,7 @@ use pumpkin_world::chunk::ChunkHeightmapType;
use rand::RngExt; use rand::RngExt;
use crate::entity::mob::{Mob, MobEntity}; use crate::entity::mob::{Mob, MobEntity};
use crate::entity::{Entity, EntityBase, NbtFuture}; use crate::entity::{Entity, EntityBase};
use crate::world::World; use crate::world::World;
const ROOSTING_FLAG: u8 = 1; const ROOSTING_FLAG: u8 = 1;
@ -217,19 +217,15 @@ impl Mob for BatEntity {
); );
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { let flags: u8 = if self.is_roosting() { ROOSTING_FLAG } else { 0 };
let flags: u8 = if self.is_roosting() { ROOSTING_FLAG } else { 0 }; nbt.put_byte("BatFlags", i8::try_from(flags).unwrap_or(0));
nbt.put_byte("BatFlags", i8::try_from(flags).unwrap_or(0));
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { let flags = u8::try_from(nbt.get_byte("BatFlags").unwrap_or(0)).unwrap_or(0);
let flags = u8::try_from(nbt.get_byte("BatFlags").unwrap_or(0)).unwrap_or(0); let roosting = (flags & ROOSTING_FLAG) != 0;
let roosting = (flags & ROOSTING_FLAG) != 0; self.set_roosting(roosting);
self.set_roosting(roosting);
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {

View File

@ -18,7 +18,7 @@ use pumpkin_util::math::vector3::Vector3;
use crate::block::entities::creaking_heart::CreakingHeartBlockEntity; use crate::block::entities::creaking_heart::CreakingHeartBlockEntity;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal, look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
@ -397,28 +397,24 @@ impl CreakingEntity {
} }
impl Mob for CreakingEntity { impl Mob for CreakingEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { if let Some(pos) = self.get_home_pos() {
if let Some(pos) = self.get_home_pos() { let mut sub = NbtCompound::new();
let mut sub = NbtCompound::new(); sub.put_int("x", pos.0.x);
sub.put_int("x", pos.0.x); sub.put_int("y", pos.0.y);
sub.put_int("y", pos.0.y); sub.put_int("z", pos.0.z);
sub.put_int("z", pos.0.z); nbt.put_compound("home_pos", sub);
nbt.put_compound("home_pos", sub); }
}
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(sub) = nbt.get_compound("home_pos")
if let Some(sub) = nbt.get_compound("home_pos") && let (Some(x), Some(y), Some(z)) =
&& let (Some(x), Some(y), Some(z)) = (sub.get_int("x"), sub.get_int("y"), sub.get_int("z"))
(sub.get_int("x"), sub.get_int("y"), sub.get_int("z")) {
{ let pos = BlockPos::new(x, y, z);
let pos = BlockPos::new(x, y, z); self.set_transient(pos);
self.set_transient(pos); }
}
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {

View File

@ -13,7 +13,7 @@ use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::{codec::var_int::VarInt, java::client::play::Metadata}; use pumpkin_protocol::{codec::var_int::VarInt, java::client::play::Metadata};
use crate::entity::{ use crate::entity::{
Entity, EntityBase, EntityBaseFuture, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, creeper_ignite::CreeperIgniteGoal, active_target::ActiveTargetGoal, creeper_ignite::CreeperIgniteGoal,
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal,
@ -101,7 +101,7 @@ impl CreeperEntity {
); );
} }
async fn explode(&self) { pub fn explode(&self) {
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
let radius = self.explosion_radius.load(Ordering::Relaxed) as f32; let radius = self.explosion_radius.load(Ordering::Relaxed) as f32;
let multiplier = if self.charged.load(Ordering::Relaxed) { let multiplier = if self.charged.load(Ordering::Relaxed) {
@ -115,75 +115,66 @@ 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 world.explode(
.explode( pos,
pos, radius * multiplier,
radius * multiplier, crate::world::ExplosionInteraction::Mob,
crate::world::ExplosionInteraction::Mob, );
)
.await;
// TODO: spawn area effect cloud with potion effects // TODO: spawn area effect cloud with potion effects
entity.remove(); entity.remove();
} }
} }
impl Mob for CreeperEntity { impl Mob for CreeperEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async { nbt.put_bool("powered", self.charged.load(Ordering::Relaxed));
nbt.put_bool("powered", self.charged.load(Ordering::Relaxed)); nbt.put_short("Fuse", self.fuse_time.load(Ordering::Relaxed) as i16);
nbt.put_short("Fuse", self.fuse_time.load(Ordering::Relaxed) as i16); nbt.put_byte(
nbt.put_byte( "ExplosionRadius",
"ExplosionRadius", self.explosion_radius.load(Ordering::Relaxed) as i8,
self.explosion_radius.load(Ordering::Relaxed) as i8, );
); nbt.put_bool("ignited", self.ignited.load(Ordering::Relaxed));
nbt.put_bool("ignited", self.ignited.load(Ordering::Relaxed));
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async { if let Some(powered) = nbt.get_bool("powered") {
if let Some(powered) = nbt.get_bool("powered") { self.charged.store(powered, Ordering::Relaxed);
self.charged.store(powered, Ordering::Relaxed); }
} if let Some(fuse) = nbt.get_short("Fuse") {
if let Some(fuse) = nbt.get_short("Fuse") { self.fuse_time.store(i32::from(fuse), Ordering::Relaxed);
self.fuse_time.store(i32::from(fuse), Ordering::Relaxed); }
} if let Some(radius) = nbt.get_byte("ExplosionRadius") {
if let Some(radius) = nbt.get_byte("ExplosionRadius") { self.explosion_radius
self.explosion_radius .store(i32::from(radius), Ordering::Relaxed);
.store(i32::from(radius), Ordering::Relaxed); }
} if let Some(ignited) = nbt.get_bool("ignited") {
if let Some(ignited) = nbt.get_bool("ignited") { self.ignited.store(ignited, Ordering::Relaxed);
self.ignited.store(ignited, Ordering::Relaxed); }
}
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {
&self.mob_entity &self.mob_entity
} }
fn mob_on_lightning_strike<'a>( fn mob_on_lightning_strike(
&'a self, &self,
caller: &'a dyn EntityBase, caller: &dyn EntityBase,
lightning: &'a crate::entity::lightning::LightningBoltEntity, lightning: &crate::entity::lightning::LightningBoltEntity,
) -> EntityBaseFuture<'a, ()> { ) {
Box::pin(async move { self.charged.store(true, Ordering::Relaxed);
self.charged.store(true, Ordering::Relaxed); self.mob_entity.living_entity.entity.send_meta_data(
self.mob_entity.living_entity.entity.send_meta_data( &[Metadata::new(
&[Metadata::new( pumpkin_data::tracked_data::creeper::CHARGED,
pumpkin_data::tracked_data::creeper::CHARGED, true,
true, )],
)], None,
None, );
); self.mob_entity
self.mob_entity .living_entity
.living_entity .on_lightning_strike(caller, lightning);
.on_lightning_strike(caller, lightning)
.await;
})
} }
fn mob_tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>) { fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() { if !entity.is_alive() {
return; return;
@ -218,52 +209,41 @@ impl Mob for CreeperEntity {
if new_fuse >= fuse_time { if new_fuse >= fuse_time {
self.current_fuse_time.store(fuse_time, Ordering::Relaxed); self.current_fuse_time.store(fuse_time, Ordering::Relaxed);
let caller_clone = caller.clone(); self.explode();
tokio::spawn(async move {
if let Some(creeper) = caller_clone.cast_any().downcast_ref::<Self>() {
creeper.explode().await;
}
});
} }
} }
fn mob_interact<'a>( fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
&'a self, if item_stack.item.id != Item::FLINT_AND_STEEL.id {
player: &'a Arc<Player>, return self.mob_entity.mob_interact(player, item_stack);
item_stack: &'a mut ItemStack, }
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move {
if item_stack.item.id != Item::FLINT_AND_STEEL.id {
return self.mob_entity.mob_interact(player, item_stack);
}
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
let world = entity.world.load(); let world = entity.world.load();
let pos = entity.pos.load(); let pos = entity.pos.load();
world.play_sound_fine( world.play_sound_fine(
Sound::ItemFlintandsteelUse, Sound::ItemFlintandsteelUse,
SoundCategory::Hostile, SoundCategory::Hostile,
&pos, &pos,
1.0, 1.0,
rand::random::<f32>() * 0.4 + 0.8, rand::random::<f32>() * 0.4 + 0.8,
); );
self.ignited.store(true, Ordering::Relaxed); self.ignited.store(true, Ordering::Relaxed);
entity.send_meta_data( entity.send_meta_data(
&[Metadata::new( &[Metadata::new(
pumpkin_data::tracked_data::creeper::IS_IGNITED, pumpkin_data::tracked_data::creeper::IS_IGNITED,
true, true,
)], )],
None, None,
); );
if player.gamemode.load() != pumpkin_util::GameMode::Creative { if player.gamemode.load() != pumpkin_util::GameMode::Creative {
// TODO: Handle DamageResult::Broken to broadcast item break and update player slot. // TODO: Handle DamageResult::Broken to broadcast item break and update player slot.
let _ = item_stack.damage_item(1); let _ = item_stack.damage_item(1);
} }
true true
})
} }
} }

View File

@ -27,7 +27,7 @@ use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::
use rand::RngExt; use rand::RngExt;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::{ ai::{
goal::{ goal::{
active_target::ActiveTargetGoal, chase_player::ChasePlayerGoal, active_target::ActiveTargetGoal, chase_player::ChasePlayerGoal,
@ -412,20 +412,16 @@ impl EndermanEntity {
} }
impl Mob for EndermanEntity { impl Mob for EndermanEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async { if let Some(block_state) = self.carried_block.load() {
if let Some(block_state) = self.carried_block.load() { nbt.put_int("carriedBlockState", block_state.as_u16() as i32);
nbt.put_int("carriedBlockState", block_state.as_u16() as i32); }
}
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async { if let Some(block_state) = nbt.get_int("carriedBlockState") {
if let Some(block_state) = nbt.get_int("carriedBlockState") { self.set_carried_block(BlockStateId::new(block_state as u16));
self.set_carried_block(BlockStateId::new(block_state as u16)); }
}
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {
@ -437,7 +433,7 @@ impl Mob for EndermanEntity {
} }
// TODO: sunlight avoidance, carried block drop on death, angerable system, ambient sound override // TODO: sunlight avoidance, carried block drop on death, angerable system, ambient sound override
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) { fn mob_tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>) {
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() { if !entity.is_alive() {
return; return;
@ -447,13 +443,7 @@ impl Mob for EndermanEntity {
let raining_at_feet = world.is_raining_at(&entity.block_pos.load()); let raining_at_feet = world.is_raining_at(&entity.block_pos.load());
let raining_at_head = world.is_raining_at(&entity.bounding_box.load().max_block_pos()); let raining_at_head = world.is_raining_at(&entity.bounding_box.load().max_block_pos());
if entity.touching_water.load(Ordering::SeqCst) || raining_at_feet || raining_at_head { if entity.touching_water.load(Ordering::SeqCst) || raining_at_feet || raining_at_head {
let entity_id = entity.entity_id; caller.damage(caller.as_ref(), 1.0, DamageType::DROWN);
let world_full = entity.world.load_full();
tokio::spawn(async move {
if let Some(entity) = world_full.get_entity_by_id(entity_id) {
entity.damage(entity.as_ref(), 1.0, DamageType::DROWN);
}
});
} }
} }

View File

@ -10,7 +10,7 @@ use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
Controls, Goal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, Controls, Goal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, swim::SwimGoal, wander_around::WanderAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal, wander_around::WanderAroundGoal,
@ -161,20 +161,16 @@ impl Mob for EvokerEntity {
Some(self) Some(self)
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.write_raider_nbt(nbt);
self.write_raider_nbt(nbt); nbt.put_int("SpellTicks", self.get_spell_casting_time());
nbt.put_int("SpellTicks", self.get_spell_casting_time());
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.read_raider_nbt(nbt);
self.read_raider_nbt(nbt); if let Some(ticks) = nbt.get_int("SpellTicks") {
if let Some(ticks) = nbt.get_int("SpellTicks") { self.set_spell_casting_time(ticks);
self.set_spell_casting_time(ticks); }
}
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {

View File

@ -15,7 +15,7 @@ use crate::entity::ai::goal::active_target::ActiveTargetGoal;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::projectile::fireball::FireballEntity; use crate::entity::projectile::fireball::FireballEntity;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::goal::{Controls, Goal}, ai::goal::{Controls, Goal},
mob::{Mob, MobEntity}, mob::{Mob, MobEntity},
}; };
@ -151,21 +151,15 @@ impl Mob for GhastEntity {
} }
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { let power = self.get_explosion_power();
let power = self.get_explosion_power(); nbt.put_byte("ExplosionPower", i8::try_from(power).unwrap_or(1));
nbt.put_byte("ExplosionPower", i8::try_from(power).unwrap_or(1));
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(power) = nbt.get_byte("ExplosionPower") {
if let Some(power) = nbt.get_byte("ExplosionPower") { self.set_explosion_power(u8::try_from(power).unwrap_or(Self::DEFAULT_EXPLOSION_POWER));
self.set_explosion_power( }
u8::try_from(power).unwrap_or(Self::DEFAULT_EXPLOSION_POWER),
);
}
})
} }
fn modify_incoming_damage(&self, amount: f32, damage_type: DamageType) -> f32 { fn modify_incoming_damage(&self, amount: f32, damage_type: DamageType) -> f32 {

View File

@ -5,7 +5,7 @@ use pumpkin_data::sound::Sound;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use crate::entity::{ use crate::entity::{
Entity, NbtFuture, Entity,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, swim::SwimGoal, wander_around::WanderAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal, wander_around::WanderAroundGoal,
@ -96,16 +96,12 @@ impl Mob for IllusionerEntity {
Some(self) Some(self)
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.write_raider_nbt(nbt);
self.write_raider_nbt(nbt);
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.read_raider_nbt(nbt);
self.read_raider_nbt(nbt);
})
} }
} }

View File

@ -1,5 +1,4 @@
use super::{Entity, EntityBase, NbtFuture, ai::pathfinder::Navigator, living::LivingEntity}; use super::{Entity, EntityBase, ai::pathfinder::Navigator, living::LivingEntity};
use crate::entity::EntityBaseFuture;
use crate::entity::ai::control::MoveControlTrait; use crate::entity::ai::control::MoveControlTrait;
use crate::entity::ai::control::look_control::LookControl; use crate::entity::ai::control::look_control::LookControl;
use crate::entity::ai::control::move_control::MoveControl; use crate::entity::ai::control::move_control::MoveControl;
@ -587,21 +586,15 @@ pub trait Mob: EntityBase + Send + Sync {
} }
/// Metadata which must accompany this mob whenever it is spawned for a Java client. /// Metadata which must accompany this mob whenever it is spawned for a Java client.
fn mob_java_spawn_metadata( fn mob_java_spawn_metadata(&self, _version: JavaMinecraftVersion) -> Option<Box<[u8]>> {
&self, None
_version: JavaMinecraftVersion,
) -> EntityBaseFuture<'_, Option<Box<[u8]>>> {
Box::pin(async { None })
} }
/// Metadata which must accompany this mob whenever it is spawned for a Bedrock client. /// Metadata which must accompany this mob whenever it is spawned for a Bedrock client.
fn mob_bedrock_spawn_metadata( fn mob_bedrock_spawn_metadata(
&self, &self,
) -> EntityBaseFuture< ) -> Option<pumpkin_protocol::bedrock::client::set_actor_data::SyncedActorDataList> {
'_, None
Option<pumpkin_protocol::bedrock::client::set_actor_data::SyncedActorDataList>,
> {
Box::pin(async { None })
} }
fn get_job_site(&self) -> Option<BlockPos> { fn get_job_site(&self) -> Option<BlockPos> {
@ -699,115 +692,105 @@ pub trait Mob: EntityBase + Send + Sync {
None None
} }
fn populate_default_equipment_slots<'a>( fn populate_default_equipment_slots(
&'a self, &self,
_world: &'a Arc<World>, _world: &Arc<World>,
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty, difficulty: &crate::entity::mob::equipment::RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> { ) {
Box::pin(async move { if rand::random::<f32>()
if rand::random::<f32>() < MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier
< MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier {
{ let mut armor_type = rand::random_range(0..3);
let mut armor_type = rand::random_range(0..3); for _ in 1..=3 {
for _ in 1..=3 { if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE {
if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE { armor_type += 1;
armor_type += 1;
}
}
let partial_chance = if difficulty.base_difficulty == Difficulty::Hard {
0.1f32
} else {
0.25f32
};
let living = &self.get_mob_entity().living_entity;
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut first = true;
for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
let current = equipment.get(slot);
if !first && rand::random::<f32>() < partial_chance {
break;
}
first = false;
if current.is_empty()
&& let Some(item) = MobEntity::get_equipment_for_slot(slot, armor_type)
{
equipment.put(slot, ItemStack::new(1, item));
}
} }
} }
})
}
fn populate_default_equipment_enchantments<'a>( let partial_chance = if difficulty.base_difficulty == Difficulty::Hard {
&'a self, 0.1f32
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty, } else {
) -> EntityBaseFuture<'a, ()> { 0.25f32
Box::pin(async move { };
self.enchant_spawned_weapon(difficulty).await;
for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
self.enchant_spawned_armor(slot, difficulty).await;
}
})
}
fn enchant_spawned_weapon<'a>(
&'a self,
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
self.enchant_spawned_equipment(
&EquipmentSlot::MAIN_HAND,
MobEntity::MAX_ENCHANTED_WEAPON_CHANCE,
difficulty,
)
}
fn enchant_spawned_armor<'a>(
&'a self,
slot: &'a EquipmentSlot,
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
self.enchant_spawned_equipment(slot, MobEntity::MAX_ENCHANTED_ARMOR_CHANCE, difficulty)
}
fn enchant_spawned_equipment<'a>(
&'a self,
slot: &'a EquipmentSlot,
chance: f32,
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let living = &self.get_mob_entity().living_entity; let living = &self.get_mob_entity().living_entity;
let mut equipment = living let mut equipment = living
.entity_equipment .entity_equipment
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(stack) = equipment.equipment.get_mut(slot) let mut first = true;
&& !stack.is_empty()
&& rand::random::<f32>() < chance * difficulty.special_multiplier for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
{ let current = equipment.get(slot);
crate::entity::mob::equipment::apply_vanilla_enchantments( if !first && rand::random::<f32>() < partial_chance {
stack, break;
slot, }
difficulty.special_multiplier, first = false;
); if current.is_empty()
&& let Some(item) = MobEntity::get_equipment_for_slot(slot, armor_type)
{
equipment.put(slot, ItemStack::new(1, item));
}
} }
}) }
} }
fn mob_write_nbt<'a>(&'a self, _nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn populate_default_equipment_enchantments(
Box::pin(async {}) &self,
difficulty: &crate::entity::mob::equipment::RegionalDifficulty,
) {
self.enchant_spawned_weapon(difficulty);
for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
self.enchant_spawned_armor(slot, difficulty);
}
} }
fn mob_read_nbt<'a>(&'a self, _nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn enchant_spawned_weapon(
Box::pin(async {}) &self,
difficulty: &crate::entity::mob::equipment::RegionalDifficulty,
) {
self.enchant_spawned_equipment(
&EquipmentSlot::MAIN_HAND,
MobEntity::MAX_ENCHANTED_WEAPON_CHANCE,
difficulty,
);
} }
fn enchant_spawned_armor(
&self,
slot: &EquipmentSlot,
difficulty: &crate::entity::mob::equipment::RegionalDifficulty,
) {
self.enchant_spawned_equipment(slot, MobEntity::MAX_ENCHANTED_ARMOR_CHANCE, difficulty);
}
fn enchant_spawned_equipment(
&self,
slot: &EquipmentSlot,
chance: f32,
difficulty: &crate::entity::mob::equipment::RegionalDifficulty,
) {
let living = &self.get_mob_entity().living_entity;
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(stack) = equipment.equipment.get_mut(slot)
&& !stack.is_empty()
&& rand::random::<f32>() < chance * difficulty.special_multiplier
{
crate::entity::mob::equipment::apply_vanilla_enchantments(
stack,
slot,
difficulty.special_multiplier,
);
}
}
fn mob_write_nbt(&self, _nbt: &mut NbtCompound) {}
fn mob_read_nbt(&self, _nbt: &NbtCompound) {}
/// Set or clear the mob's target. Override to add side effects when targeting changes. /// Set or clear the mob's target. Override to add side effects when targeting changes.
fn set_mob_target(&self, target: Option<Arc<dyn EntityBase>>) { fn set_mob_target(&self, target: Option<Arc<dyn EntityBase>>) {
let mob = self.get_mob_entity(); let mob = self.get_mob_entity();
@ -817,155 +800,127 @@ pub trait Mob: EntityBase + Send + Sync {
.unwrap_or_else(std::sync::PoisonError::into_inner) = target; .unwrap_or_else(std::sync::PoisonError::into_inner) = target;
let world = mob.living_entity.entity.world.load_full(); let world = mob.living_entity.entity.world.load_full();
let entity_id = mob.living_entity.entity.entity_id; let entity_id = mob.living_entity.entity.entity_id;
tokio::spawn(async move { if let Some(server) = world.server.upgrade() {
let mut event = let mut event =
crate::plugin::api::events::entity::entity_target::EntityTargetEvent::new( crate::plugin::api::events::entity::entity_target::EntityTargetEvent::new(
entity_id, target_id, entity_id, target_id,
); );
if let Some(server) = world.server.upgrade() { server.plugin_manager.fire_blocking(&server, &mut event);
server.plugin_manager.fire(&server, &mut event).await; }
}
});
} }
fn mob_interact<'a>( fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
&'a self, self.get_mob_entity().mob_interact(player, item_stack)
player: &'a Arc<Player>,
item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { self.get_mob_entity().mob_interact(player, item_stack) })
} }
fn tame<'a>(&'a self, player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> { fn tame(&self, player: &Arc<Player>) {
Box::pin(async move { let mob = self.get_mob_entity();
let mob = self.get_mob_entity(); let mut event = crate::plugin::api::events::entity::entity_tame::EntityTameEvent::new(
let mut event = crate::plugin::api::events::entity::entity_tame::EntityTameEvent::new( mob.living_entity.entity.entity_id,
mob.living_entity.entity.entity_id, player.clone(),
player.clone(), );
); if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() { server.plugin_manager.fire_blocking(&server, &mut event);
server.plugin_manager.fire(&server, &mut event).await; }
}
})
} }
fn breed(&self, father_id: i32, mother_id: i32, child_id: i32) -> EntityBaseFuture<'_, ()> { fn breed(&self, father_id: i32, mother_id: i32, child_id: i32) {
Box::pin(async move { let mob = self.get_mob_entity();
let mob = self.get_mob_entity(); let mut event = crate::plugin::api::events::entity::entity_breed::EntityBreedEvent::new(
let mut event = crate::plugin::api::events::entity::entity_breed::EntityBreedEvent::new( father_id, mother_id, child_id,
father_id, mother_id, child_id, );
); if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() { server.plugin_manager.fire_blocking(&server, &mut event);
server.plugin_manager.fire(&server, &mut event).await; }
}
})
} }
fn dye<'a>( fn dye(
&'a self,
color: crate::plugin::api::events::entity::entity_dye::DyeColor,
player: Option<&'a Arc<Player>>,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_dye::EntityDyeEvent::new(
mob.living_entity.entity.entity_id,
color,
player.cloned(),
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
}
fn enter_love_mode(
&self, &self,
human_entity_id: Option<i32>, color: crate::plugin::api::events::entity::entity_dye::DyeColor,
ticks_in_love: i32, player: Option<&Arc<Player>>,
) -> EntityBaseFuture<'_, ()> { ) {
Box::pin(async move { let mob = self.get_mob_entity();
let mob = self.get_mob_entity(); let mut event = crate::plugin::api::events::entity::entity_dye::EntityDyeEvent::new(
let mut event = crate::plugin::api::events::entity::entity_enter_love_mode::EntityEnterLoveModeEvent::new( mob.living_entity.entity.entity_id,
color,
player.cloned(),
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn enter_love_mode(&self, human_entity_id: Option<i32>, ticks_in_love: i32) {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_enter_love_mode::EntityEnterLoveModeEvent::new(
mob.living_entity.entity.entity_id,
human_entity_id,
ticks_in_love,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn transform(&self, new_entity_id: i32, transform_reason: String) {
let mob = self.get_mob_entity();
let mut event =
crate::plugin::api::events::entity::entity_transform::EntityTransformEvent::new(
mob.living_entity.entity.entity_id, mob.living_entity.entity.entity_id,
human_entity_id, new_entity_id,
ticks_in_love, transform_reason,
); );
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() { if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire_blocking(&server, &mut event);
} }
})
} }
fn transform(&self, new_entity_id: i32, transform_reason: String) -> EntityBaseFuture<'_, ()> { fn break_door(&self, block_pos: BlockPos) {
Box::pin(async move { let mob = self.get_mob_entity();
let mob = self.get_mob_entity(); let mut event =
let mut event = crate::plugin::api::events::entity::entity_break_door::EntityBreakDoorEvent::new(
crate::plugin::api::events::entity::entity_transform::EntityTransformEvent::new(
mob.living_entity.entity.entity_id,
new_entity_id,
transform_reason,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
}
fn break_door(&self, block_pos: BlockPos) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event =
crate::plugin::api::events::entity::entity_break_door::EntityBreakDoorEvent::new(
mob.living_entity.entity.entity_id,
block_pos,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
}
fn enter_block(&self, block_pos: BlockPos) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event =
crate::plugin::api::events::entity::entity_enter_block::EntityEnterBlockEvent::new(
mob.living_entity.entity.entity_id,
block_pos,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
}
fn interact(&self, block_pos: BlockPos) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event =
crate::plugin::api::events::entity::entity_interact::EntityInteractEvent::new(
mob.living_entity.entity.entity_id,
block_pos,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
}
fn place_block(&self, block_pos: BlockPos, block_name: String) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_place::EntityPlaceEvent::new(
mob.living_entity.entity.entity_id, mob.living_entity.entity.entity_id,
block_pos, block_pos,
block_name,
); );
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() { if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire_blocking(&server, &mut event);
} }
}) }
fn enter_block(&self, block_pos: BlockPos) {
let mob = self.get_mob_entity();
let mut event =
crate::plugin::api::events::entity::entity_enter_block::EntityEnterBlockEvent::new(
mob.living_entity.entity.entity_id,
block_pos,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn interact(&self, block_pos: BlockPos) {
let mob = self.get_mob_entity();
let mut event =
crate::plugin::api::events::entity::entity_interact::EntityInteractEvent::new(
mob.living_entity.entity.entity_id,
block_pos,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn place_block(&self, block_pos: BlockPos, block_name: String) {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_place::EntityPlaceEvent::new(
mob.living_entity.entity.entity_id,
block_pos,
block_name,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
} }
fn mob_player_collision(&self, _player: &Arc<Player>) {} fn mob_player_collision(&self, _player: &Arc<Player>) {}
@ -1006,17 +961,14 @@ pub trait Mob: EntityBase + Send + Sync {
None None
} }
fn mob_on_lightning_strike<'a>( fn mob_on_lightning_strike(
&'a self, &self,
caller: &'a dyn EntityBase, caller: &dyn EntityBase,
lightning: &'a crate::entity::lightning::LightningBoltEntity, lightning: &crate::entity::lightning::LightningBoltEntity,
) -> EntityBaseFuture<'a, ()> { ) {
Box::pin(async move { self.get_mob_entity()
self.get_mob_entity() .living_entity
.living_entity .on_lightning_strike(caller, lightning);
.on_lightning_strike(caller, lightning)
.await;
})
} }
} }
impl<T: Mob + Send + 'static> EntityBase for T { impl<T: Mob + Send + 'static> EntityBase for T {
@ -1024,14 +976,12 @@ impl<T: Mob + Send + 'static> EntityBase for T {
Some(self) Some(self)
} }
fn on_lightning_strike<'a>( fn on_lightning_strike(
&'a self, &self,
caller: &'a dyn EntityBase, caller: &dyn EntityBase,
lightning: &'a crate::entity::lightning::LightningBoltEntity, lightning: &crate::entity::lightning::LightningBoltEntity,
) -> EntityBaseFuture<'a, ()> { ) {
Box::pin(async move { self.mob_on_lightning_strike(caller, lightning);
self.mob_on_lightning_strike(caller, lightning).await;
})
} }
fn get_item_steerable(&self) -> Option<&dyn crate::entity::item_steerable::ItemSteerable> { fn get_item_steerable(&self) -> Option<&dyn crate::entity::item_steerable::ItemSteerable> {
@ -1238,12 +1188,8 @@ impl<T: Mob + Send + 'static> EntityBase for T {
damaged damaged
} }
fn interact<'a>( fn interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
&'a self, self.mob_interact(player, item_stack)
player: &'a Arc<Player>,
item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { self.mob_interact(player, item_stack).await })
} }
fn on_player_collision(&self, player: &Arc<Player>) { fn on_player_collision(&self, player: &Arc<Player>) {
@ -1293,36 +1239,32 @@ impl<T: Mob + Send + 'static> EntityBase for T {
<T as Mob>::get_home(self) <T as Mob>::get_home(self)
} }
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.get_mob_entity().write_mob_nbt(nbt);
self.get_mob_entity().write_mob_nbt(nbt); if let Some(ageable) = self.as_ageable() {
if let Some(ageable) = self.as_ageable() { ageable.write_ageable_nbt(nbt);
ageable.write_ageable_nbt(nbt); }
} if let Some(animal) = self.as_animal() {
if let Some(animal) = self.as_animal() { animal.write_animal_nbt(nbt);
animal.write_animal_nbt(nbt); }
} if let Some(tamable) = self.as_tamable() {
if let Some(tamable) = self.as_tamable() { tamable.write_tamable_nbt(nbt);
tamable.write_tamable_nbt(nbt); }
} self.mob_write_nbt(nbt);
self.mob_write_nbt(nbt).await;
})
} }
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn read_custom_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.get_mob_entity().read_mob_nbt(nbt);
self.get_mob_entity().read_mob_nbt(nbt); if let Some(ageable) = self.as_ageable() {
if let Some(ageable) = self.as_ageable() { ageable.read_ageable_nbt(nbt);
ageable.read_ageable_nbt(nbt); }
} if let Some(animal) = self.as_animal() {
if let Some(animal) = self.as_animal() { animal.read_animal_nbt(nbt);
animal.read_animal_nbt(nbt); }
} if let Some(tamable) = self.as_tamable() {
if let Some(tamable) = self.as_tamable() { tamable.read_tamable_nbt(nbt);
tamable.read_tamable_nbt(nbt); }
} self.mob_read_nbt(nbt);
self.mob_read_nbt(nbt).await;
})
} }
fn get_gravity(&self) -> f64 { fn get_gravity(&self) -> f64 {

View File

@ -1,5 +1,5 @@
use std::sync::{ use std::sync::{
Arc, Weak, Arc, Mutex, Weak,
atomic::{AtomicBool, AtomicI32, Ordering}, atomic::{AtomicBool, AtomicI32, Ordering},
}; };
@ -16,12 +16,11 @@ use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::java::client::play::Metadata; use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::boundingbox::EntityDimensions; use pumpkin_util::math::boundingbox::EntityDimensions;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use tokio::sync::Mutex;
use crate::entity::living::LivingEntity; use crate::entity::living::LivingEntity;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, EntityBaseFuture, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal, look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal,
@ -274,10 +273,13 @@ impl PiglinEntity {
self.eat_cooldown_timer.load(Ordering::Relaxed) > 0 self.eat_cooldown_timer.load(Ordering::Relaxed) > 0
} }
pub async fn start_admiring(&self, item: ItemStack) { pub fn start_admiring(&self, item: ItemStack) {
self.admire_timer self.admire_timer
.store(PiglinAi::ADMIRE_DURATION, Ordering::Relaxed); .store(PiglinAi::ADMIRE_DURATION, Ordering::Relaxed);
*self.admiring_item.lock().await = Some(item.clone()); *self
.admiring_item
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(item.clone());
let mut equip = self let mut equip = self
.mob_entity .mob_entity
@ -296,9 +298,12 @@ impl PiglinEntity {
); );
} }
pub async fn stop_holding_off_hand_item(&self, bartering_enabled: bool) { pub fn stop_holding_off_hand_item(&self, bartering_enabled: bool) {
let admired_item = { let admired_item = {
let mut guard = self.admiring_item.lock().await; let mut guard = self
.admiring_item
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take() guard.take()
}; };
@ -329,31 +334,34 @@ impl PiglinEntity {
outcomes, outcomes,
); );
if let Some(server) = entity.world.load().server.upgrade() { if let Some(server) = entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await; server.plugin_manager.fire_blocking(&server, &mut event);
} }
if !event.cancelled { if !event.cancelled {
PiglinAi::throw_items(self, event.outcome, None); PiglinAi::throw_items(self, event.outcome, None);
} }
} else if !is_barter { } else if !is_barter {
let remainder = self.add_to_inventory(item).await; let remainder = self.add_to_inventory(item);
if let Some(rem) = remainder { if let Some(rem) = remainder {
PiglinAi::throw_items(self, vec![rem], None); PiglinAi::throw_items(self, vec![rem], None);
} }
} }
} else { } else {
let remainder = self.add_to_inventory(item).await; let remainder = self.add_to_inventory(item);
if let Some(rem) = remainder { if let Some(rem) = remainder {
PiglinAi::throw_items(self, vec![rem], None); PiglinAi::throw_items(self, vec![rem], None);
} }
} }
} }
pub async fn cancel_admiring(&self) { pub fn cancel_admiring(&self) {
if self.is_admiring() { if self.is_admiring() {
self.admire_timer.store(0, Ordering::Relaxed); self.admire_timer.store(0, Ordering::Relaxed);
let item = { let item = {
let mut guard = self.admiring_item.lock().await; let mut guard = self
.admiring_item
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.take() guard.take()
}; };
if let Some(item) = item { if let Some(item) = item {
@ -371,8 +379,8 @@ impl PiglinEntity {
} }
} }
pub async fn was_hurt_by(&self, attacker: Option<&dyn EntityBase>) { pub fn was_hurt_by(&self, attacker: Option<&dyn EntityBase>) {
self.cancel_admiring().await; self.cancel_admiring();
self.set_dancing(false); self.set_dancing(false);
self.celebration_timer.store(0, Ordering::Relaxed); self.celebration_timer.store(0, Ordering::Relaxed);
@ -384,8 +392,11 @@ impl PiglinEntity {
} }
} }
pub async fn add_to_inventory(&self, item: ItemStack) -> Option<ItemStack> { pub fn add_to_inventory(&self, item: ItemStack) -> Option<ItemStack> {
let mut inv = self.inventory.lock().await; let mut inv = self
.inventory
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if inv.len() < Self::INVENTORY_SIZE { if inv.len() < Self::INVENTORY_SIZE {
inv.push(item); inv.push(item);
None None
@ -483,53 +494,51 @@ impl Mob for PiglinEntity {
&self.mob_entity &self.mob_entity
} }
fn populate_default_equipment_slots<'a>( fn populate_default_equipment_slots(
&'a self, &self,
_world: &'a Arc<World>, _world: &Arc<World>,
_difficulty: &'a RegionalDifficulty, _difficulty: &RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> { ) {
Box::pin(async move { if !self.is_baby.load(Ordering::Relaxed) {
if !self.is_baby.load(Ordering::Relaxed) { let living = &self.mob_entity.living_entity;
let living = &self.mob_entity.living_entity; let mut equipment = living
let mut equipment = living .entity_equipment
.entity_equipment .lock()
.lock() .unwrap_or_else(std::sync::PoisonError::into_inner);
.unwrap_or_else(std::sync::PoisonError::into_inner);
// Spawn weapon: 50% crossbow, 5% golden spear (10% of remaining 50%), 45% golden sword // Spawn weapon: 50% crossbow, 5% golden spear (10% of remaining 50%), 45% golden sword
let weapon = if rand::random::<f32>() < 0.5 { let weapon = if rand::random::<f32>() < 0.5 {
&Item::CROSSBOW &Item::CROSSBOW
} else if rand::random_range(0..10) == 0 { } else if rand::random_range(0..10) == 0 {
&Item::GOLDEN_SPEAR &Item::GOLDEN_SPEAR
} else { } else {
&Item::GOLDEN_SWORD &Item::GOLDEN_SWORD
}; };
equipment.put(&EquipmentSlot::MAIN_HAND, ItemStack::new(1, weapon)); equipment.put(&EquipmentSlot::MAIN_HAND, ItemStack::new(1, weapon));
// Armor: 10% chance per piece for golden armor // Armor: 10% chance per piece for golden armor
if rand::random::<f32>() < 0.1 { if rand::random::<f32>() < 0.1 {
equipment.put( equipment.put(
&EquipmentSlot::HEAD, &EquipmentSlot::HEAD,
ItemStack::new(1, &Item::GOLDEN_HELMET), ItemStack::new(1, &Item::GOLDEN_HELMET),
); );
}
if rand::random::<f32>() < 0.1 {
equipment.put(
&EquipmentSlot::CHEST,
ItemStack::new(1, &Item::GOLDEN_CHESTPLATE),
);
}
if rand::random::<f32>() < 0.1 {
equipment.put(
&EquipmentSlot::LEGS,
ItemStack::new(1, &Item::GOLDEN_LEGGINGS),
);
}
if rand::random::<f32>() < 0.1 {
equipment.put(&EquipmentSlot::FEET, ItemStack::new(1, &Item::GOLDEN_BOOTS));
}
} }
}) if rand::random::<f32>() < 0.1 {
equipment.put(
&EquipmentSlot::CHEST,
ItemStack::new(1, &Item::GOLDEN_CHESTPLATE),
);
}
if rand::random::<f32>() < 0.1 {
equipment.put(
&EquipmentSlot::LEGS,
ItemStack::new(1, &Item::GOLDEN_LEGGINGS),
);
}
if rand::random::<f32>() < 0.1 {
equipment.put(&EquipmentSlot::FEET, ItemStack::new(1, &Item::GOLDEN_BOOTS));
}
}
} }
fn mob_init_data_tracker(&self) { fn mob_init_data_tracker(&self) {
@ -558,100 +567,96 @@ impl Mob for PiglinEntity {
} }
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { if self.is_immune_to_zombification() {
if self.is_immune_to_zombification() { nbt.put_bool("IsImmuneToZombification", true);
nbt.put_bool("IsImmuneToZombification", true); }
} let time_in_overworld = self.time_in_overworld.load(Ordering::Relaxed);
let time_in_overworld = self.time_in_overworld.load(Ordering::Relaxed); if time_in_overworld > 0 {
if time_in_overworld > 0 { nbt.put_int("TimeInOverworld", time_in_overworld);
nbt.put_int("TimeInOverworld", time_in_overworld); }
} nbt.put_bool("CanPickUpLoot", true);
nbt.put_bool("CanPickUpLoot", true); if self.is_baby() {
if self.is_baby() { nbt.put_bool("IsBaby", true);
nbt.put_bool("IsBaby", true); }
} if !self.can_hunt() {
if !self.can_hunt() { nbt.put_bool("CannotHunt", true);
nbt.put_bool("CannotHunt", true); }
} if self.is_charging_crossbow() {
if self.is_charging_crossbow() { nbt.put_bool("IsChargingCrossbow", true);
nbt.put_bool("IsChargingCrossbow", true); }
} if self.is_dancing() {
if self.is_dancing() { nbt.put_bool("IsDancing", true);
nbt.put_bool("IsDancing", true); }
}
let inv = self.inventory.lock().await; let inv = self
if !inv.is_empty() { .inventory
let mut items_tag = Vec::new(); .lock()
for item in inv.iter() { .unwrap_or_else(std::sync::PoisonError::into_inner);
if !item.is_empty() { if !inv.is_empty() {
let mut item_nbt = NbtCompound::new(); let mut items_tag = Vec::new();
item.write_item_stack(&mut item_nbt); for item in inv.iter() {
items_tag.push(NbtTag::Compound(item_nbt)); if !item.is_empty() {
} let mut item_nbt = NbtCompound::new();
} item.write_item_stack(&mut item_nbt);
if !items_tag.is_empty() { items_tag.push(NbtTag::Compound(item_nbt));
nbt.put_list("Inventory", items_tag);
} }
} }
}) if !items_tag.is_empty() {
nbt.put_list("Inventory", items_tag);
}
}
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(immune) = nbt.get_bool("IsImmuneToZombification") {
if let Some(immune) = nbt.get_bool("IsImmuneToZombification") { self.set_immune_to_zombification(immune);
self.set_immune_to_zombification(immune); }
} if let Some(time) = nbt.get_int("TimeInOverworld") {
if let Some(time) = nbt.get_int("TimeInOverworld") { self.time_in_overworld.store(time, Ordering::Relaxed);
self.time_in_overworld.store(time, Ordering::Relaxed); }
} if let Some(baby) = nbt.get_bool("IsBaby") {
if let Some(baby) = nbt.get_bool("IsBaby") { self.set_baby(baby);
self.set_baby(baby); }
} if let Some(cannot_hunt) = nbt.get_bool("CannotHunt") {
if let Some(cannot_hunt) = nbt.get_bool("CannotHunt") { self.set_cannot_hunt(cannot_hunt);
self.set_cannot_hunt(cannot_hunt); }
} if let Some(charging) = nbt.get_bool("IsChargingCrossbow") {
if let Some(charging) = nbt.get_bool("IsChargingCrossbow") { self.set_charging_crossbow(charging);
self.set_charging_crossbow(charging); }
} if let Some(dancing) = nbt.get_bool("IsDancing") {
if let Some(dancing) = nbt.get_bool("IsDancing") { self.set_dancing(dancing);
self.set_dancing(dancing); }
} if let Some(inv_list) = nbt.get_list("Inventory") {
if let Some(inv_list) = nbt.get_list("Inventory") { let mut inv = self
let mut inv = self.inventory.lock().await; .inventory
inv.clear(); .lock()
for tag in inv_list { .unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(compound) = tag.extract_compound() inv.clear();
&& let Some(stack) = ItemStack::read_item_stack(compound) for tag in inv_list {
{ if let Some(compound) = tag.extract_compound()
inv.push(stack); && let Some(stack) = ItemStack::read_item_stack(compound)
} {
inv.push(stack);
} }
} }
}) }
} }
fn mob_interact<'a>( fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
&'a self, if PiglinAi::can_admire(self, item_stack) {
player: &'a Arc<Player>, let mut given = item_stack.clone();
item_stack: &'a mut ItemStack, given.item_count = 1;
) -> EntityBaseFuture<'a, bool> { if player.gamemode.load() != pumpkin_util::GameMode::Creative {
Box::pin(async move { item_stack.item_count -= 1;
if PiglinAi::can_admire(self, item_stack) {
let mut given = item_stack.clone();
given.item_count = 1;
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
item_stack.item_count -= 1;
}
self.start_admiring(given).await;
return true;
} }
self.mob_entity.mob_interact(player, item_stack) self.start_admiring(given);
}) return true;
}
self.mob_entity.mob_interact(player, item_stack)
} }
fn mob_tick<'a>(&'a self, caller: &'a Arc<dyn EntityBase>) { fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
let entity = &self.mob_entity.living_entity.entity; let entity = &self.mob_entity.living_entity.entity;
if !entity.is_alive() { if !entity.is_alive() {
return; return;
@ -686,12 +691,7 @@ impl Mob for PiglinEntity {
if self.admire_timer.load(Ordering::Relaxed) > 0 { if self.admire_timer.load(Ordering::Relaxed) > 0 {
let remaining = self.admire_timer.fetch_sub(1, Ordering::Relaxed) - 1; let remaining = self.admire_timer.fetch_sub(1, Ordering::Relaxed) - 1;
if remaining <= 0 { if remaining <= 0 {
let caller_clone = caller.clone(); self.stop_holding_off_hand_item(true);
tokio::spawn(async move {
if let Some(piglin) = caller_clone.cast_any().downcast_ref::<Self>() {
piglin.stop_holding_off_hand_item(true).await;
}
});
} }
} }
} }

View File

@ -13,7 +13,7 @@ use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal, look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, open_door::OpenDoorGoal,
@ -196,28 +196,24 @@ impl Mob for PiglinBruteEntity {
} }
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { if self.is_immune_to_zombification() {
if self.is_immune_to_zombification() { nbt.put_bool("IsImmuneToZombification", true);
nbt.put_bool("IsImmuneToZombification", true); }
} let time_in_overworld = self.time_in_overworld.load(Ordering::Relaxed);
let time_in_overworld = self.time_in_overworld.load(Ordering::Relaxed); if time_in_overworld > 0 {
if time_in_overworld > 0 { nbt.put_int("TimeInOverworld", time_in_overworld);
nbt.put_int("TimeInOverworld", time_in_overworld); }
} nbt.put_bool("CanPickUpLoot", true);
nbt.put_bool("CanPickUpLoot", true);
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(immune) = nbt.get_bool("IsImmuneToZombification") {
if let Some(immune) = nbt.get_bool("IsImmuneToZombification") { self.set_immune_to_zombification(immune);
self.set_immune_to_zombification(immune); }
} if let Some(time) = nbt.get_int("TimeInOverworld") {
if let Some(time) = nbt.get_int("TimeInOverworld") { self.time_in_overworld.store(time, Ordering::Relaxed);
self.time_in_overworld.store(time, Ordering::Relaxed); }
}
})
} }
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) { fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {

View File

@ -1,5 +1,5 @@
use std::sync::{ use std::sync::{
Arc, Weak, Arc, Mutex, Weak,
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
}; };
@ -10,10 +10,9 @@ use pumpkin_data::tracked_data;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag; use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::java::client::play::Metadata; use pumpkin_protocol::java::client::play::Metadata;
use tokio::sync::Mutex;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, ranged_crossbow_attack::RangedCrossbowAttackGoal, look_at_entity::LookAtEntityGoal, ranged_crossbow_attack::RangedCrossbowAttackGoal,
@ -115,8 +114,11 @@ impl PillagerEntity {
); );
} }
pub async fn add_to_inventory(&self, item: ItemStack) -> Option<ItemStack> { pub fn add_to_inventory(&self, item: ItemStack) -> Option<ItemStack> {
let mut inv = self.inventory.lock().await; let mut inv = self
.inventory
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if inv.len() < Self::INVENTORY_SIZE { if inv.len() < Self::INVENTORY_SIZE {
inv.push(item); inv.push(item);
None None
@ -163,56 +165,49 @@ impl Mob for PillagerEntity {
} }
fn mob_init_data_tracker(&self) { fn mob_init_data_tracker(&self) {
let entity = self.get_entity(); self.set_charging_crossbow(self.is_charging_crossbow.load(Ordering::Relaxed));
if self.is_charging_crossbow() { }
entity.send_meta_data(
&[Metadata::new( fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
tracked_data::pillager::IS_CHARGING_CROSSBOW, self.write_raider_nbt(nbt);
true, nbt.put_bool("CanPickUpLoot", true);
)],
None, let inv = self
); .inventory
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if !inv.is_empty() {
let mut items_tag = Vec::new();
for item in inv.iter() {
if !item.is_empty() {
let mut item_nbt = NbtCompound::new();
item.write_item_stack(&mut item_nbt);
items_tag.push(NbtTag::Compound(item_nbt));
}
}
if !items_tag.is_empty() {
nbt.put_list("Inventory", items_tag);
}
} }
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.read_raider_nbt(nbt);
self.write_raider_nbt(nbt);
nbt.put_bool("CanPickUpLoot", true);
let inv = self.inventory.lock().await; if let Some(inv_list) = nbt.get_list("Inventory") {
if !inv.is_empty() { let mut inv = self
let mut items_tag = Vec::new(); .inventory
for item in inv.iter() { .lock()
if !item.is_empty() { .unwrap_or_else(std::sync::PoisonError::into_inner);
let mut item_nbt = NbtCompound::new(); inv.clear();
item.write_item_stack(&mut item_nbt); for tag in inv_list {
items_tag.push(NbtTag::Compound(item_nbt)); if let Some(compound) = tag.extract_compound()
} && let Some(stack) = ItemStack::read_item_stack(compound)
} {
if !items_tag.is_empty() { inv.push(stack);
nbt.put_list("Inventory", items_tag);
} }
} }
}) }
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.read_raider_nbt(nbt);
if let Some(inv_list) = nbt.get_list("Inventory") {
let mut inv = self.inventory.lock().await;
inv.clear();
for tag in inv_list {
if let Some(compound) = tag.extract_compound()
&& let Some(stack) = ItemStack::read_item_stack(compound)
{
inv.push(stack);
}
}
}
})
} }
fn on_damage( fn on_damage(

View File

@ -5,7 +5,7 @@ use pumpkin_data::sound::Sound;
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use crate::entity::{ use crate::entity::{
Entity, NbtFuture, Entity,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal, look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
@ -97,16 +97,12 @@ impl Mob for RavagerEntity {
Some(self) Some(self)
} }
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { self.write_raider_nbt(nbt);
self.write_raider_nbt(nbt);
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.read_raider_nbt(nbt);
self.read_raider_nbt(nbt);
})
} }
} }

View File

@ -21,7 +21,7 @@ use crate::entity::ai::goal::revenge::RevengeGoal;
use crate::entity::ai::goal::{Controls, Goal}; use crate::entity::ai::goal::{Controls, Goal};
use crate::entity::mob::{Mob, MobEntity}; use crate::entity::mob::{Mob, MobEntity};
use crate::entity::projectile::shulker_bullet::ShulkerBulletEntity; use crate::entity::projectile::shulker_bullet::ShulkerBulletEntity;
use crate::entity::{Entity, EntityBase, NbtFuture}; use crate::entity::{Entity, EntityBase};
const DEFAULT_ATTACH_FACE: BlockDirection = BlockDirection::Down; const DEFAULT_ATTACH_FACE: BlockDirection = BlockDirection::Down;
const NO_COLOR: u8 = 16; const NO_COLOR: u8 = 16;
@ -322,26 +322,22 @@ impl ShulkerEntity {
} }
impl Mob for ShulkerEntity { impl Mob for ShulkerEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { nbt.put_byte("AttachFace", self.attach_face.load(Ordering::Relaxed) as i8);
nbt.put_byte("AttachFace", self.attach_face.load(Ordering::Relaxed) as i8); nbt.put_byte("PeekAmount", self.peek_amount.load(Ordering::Relaxed) as i8);
nbt.put_byte("PeekAmount", self.peek_amount.load(Ordering::Relaxed) as i8); nbt.put_byte("Color", self.color.load(Ordering::Relaxed) as i8);
nbt.put_byte("Color", self.color.load(Ordering::Relaxed) as i8);
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { if let Some(face) = nbt.get_byte("AttachFace") {
if let Some(face) = nbt.get_byte("AttachFace") { self.attach_face.store(face as u8, Ordering::Relaxed);
self.attach_face.store(face as u8, Ordering::Relaxed); }
} if let Some(peek) = nbt.get_byte("PeekAmount") {
if let Some(peek) = nbt.get_byte("PeekAmount") { self.peek_amount.store(peek as u8, Ordering::Relaxed);
self.peek_amount.store(peek as u8, Ordering::Relaxed); }
} if let Some(color) = nbt.get_byte("Color") {
if let Some(color) = nbt.get_byte("Color") { self.color.store(color as u8, Ordering::Relaxed);
self.color.store(color as u8, Ordering::Relaxed); }
}
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {

View File

@ -7,7 +7,7 @@ use pumpkin_data::item_stack::ItemStack;
use pumpkin_util::Difficulty; use pumpkin_util::Difficulty;
use crate::entity::{ use crate::entity::{
Entity, EntityBaseFuture, Entity,
ai::goal::{ ai::goal::{
active_target::ActiveTargetGoal, bow_attack::BowAttackGoal, active_target::ActiveTargetGoal, bow_attack::BowAttackGoal,
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal,
@ -76,57 +76,55 @@ impl Mob for SkeletonEntityBase {
&self.mob_entity &self.mob_entity
} }
fn populate_default_equipment_slots<'a>( fn populate_default_equipment_slots(
&'a self, &self,
_world: &'a Arc<World>, _world: &Arc<World>,
difficulty: &'a RegionalDifficulty, difficulty: &RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> { ) {
Box::pin(async move { // Default armor slots (super.populateDefaultEquipmentSlots)
// Default armor slots (super.populateDefaultEquipmentSlots) if rand::random::<f32>()
if rand::random::<f32>() < MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier
< MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier {
{ let mut armor_type = rand::random_range(0..3);
let mut armor_type = rand::random_range(0..3); for _ in 1..=3 {
for _ in 1..=3 { if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE {
if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE { armor_type += 1;
armor_type += 1;
}
}
let partial_chance = if difficulty.base_difficulty == Difficulty::Hard {
0.1f32
} else {
0.25f32
};
let living = &self.mob_entity.living_entity;
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut first = true;
for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
let current = equipment.get(slot);
if !first && rand::random::<f32>() < partial_chance {
break;
}
first = false;
if current.is_empty()
&& let Some(item) = MobEntity::get_equipment_for_slot(slot, armor_type)
{
equipment.put(slot, ItemStack::new(1, item));
}
} }
} }
// AbstractSkeleton sets BOW on MAIN_HAND let partial_chance = if difficulty.base_difficulty == Difficulty::Hard {
0.1f32
} else {
0.25f32
};
let living = &self.mob_entity.living_entity; let living = &self.mob_entity.living_entity;
let mut equipment = living let mut equipment = living
.entity_equipment .entity_equipment
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner); .unwrap_or_else(std::sync::PoisonError::into_inner);
equipment.put(&EquipmentSlot::MAIN_HAND, ItemStack::new(1, &Item::BOW)); let mut first = true;
})
for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
let current = equipment.get(slot);
if !first && rand::random::<f32>() < partial_chance {
break;
}
first = false;
if current.is_empty()
&& let Some(item) = MobEntity::get_equipment_for_slot(slot, armor_type)
{
equipment.put(slot, ItemStack::new(1, item));
}
}
}
// AbstractSkeleton sets BOW on MAIN_HAND
let living = &self.mob_entity.living_entity;
let mut equipment = living
.entity_equipment
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
equipment.put(&EquipmentSlot::MAIN_HAND, ItemStack::new(1, &Item::BOW));
} }
} }

View File

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use crate::entity::{ use crate::entity::{
Entity, EntityBaseFuture, Entity,
mob::{Mob, MobEntity, equipment::RegionalDifficulty, skeleton::SkeletonEntityBase}, mob::{Mob, MobEntity, equipment::RegionalDifficulty, skeleton::SkeletonEntityBase},
}; };
use crate::world::World; use crate::world::World;
@ -23,20 +23,17 @@ impl Mob for SkeletonEntity {
&self.entity.mob_entity &self.entity.mob_entity
} }
fn populate_default_equipment_slots<'a>( fn populate_default_equipment_slots(
&'a self, &self,
world: &'a Arc<World>, world: &Arc<World>,
difficulty: &'a RegionalDifficulty, difficulty: &RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> { ) {
self.entity self.entity
.populate_default_equipment_slots(world, difficulty) .populate_default_equipment_slots(world, difficulty);
} }
fn populate_default_equipment_enchantments<'a>( fn populate_default_equipment_enchantments(&self, difficulty: &RegionalDifficulty) {
&'a self,
difficulty: &'a RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
self.entity self.entity
.populate_default_equipment_enchantments(difficulty) .populate_default_equipment_enchantments(difficulty);
} }
} }

View File

@ -12,7 +12,7 @@ use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use crate::entity::{ use crate::entity::{
Entity, EntityBase, NbtFuture, Entity, EntityBase,
ai::control::{Control, MoveControlTrait}, ai::control::{Control, MoveControlTrait},
ai::goal::{Goal, active_target::ActiveTargetGoal}, ai::goal::{Goal, active_target::ActiveTargetGoal},
mob::{Mob, MobEntity}, mob::{Mob, MobEntity},
@ -265,21 +265,17 @@ impl SlimeEntity {
} }
impl Mob for SlimeEntity { impl Mob for SlimeEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
Box::pin(async move { nbt.put_int("Size", self.get_size() - 1);
nbt.put_int("Size", self.get_size() - 1); nbt.put_bool("wasOnGround", self.was_on_ground.load(Ordering::Relaxed));
nbt.put_bool("wasOnGround", self.was_on_ground.load(Ordering::Relaxed));
})
} }
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { fn mob_read_nbt(&self, nbt: &NbtCompound) {
Box::pin(async move { self.set_size(nbt.get_int("Size").unwrap_or(0) + 1, false);
self.set_size(nbt.get_int("Size").unwrap_or(0) + 1, false); self.was_on_ground.store(
self.was_on_ground.store( nbt.get_bool("wasOnGround").unwrap_or(false),
nbt.get_bool("wasOnGround").unwrap_or(false), Ordering::Relaxed,
Ordering::Relaxed, );
);
})
} }
fn get_mob_entity(&self) -> &MobEntity { fn get_mob_entity(&self) -> &MobEntity {

Some files were not shown because too many files have changed in this diff Show More