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

View File

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

View File

@@ -35,7 +35,7 @@ impl ScreenHandlerFactory for EnderChestScreenFactory {
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
if let Some(tracker) = &self.tracker {
self.inventory.set_tracker(tracker.clone()).await;
self.inventory.set_tracker(tracker.clone());
}
let handler =
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),
));
args.world.spawn_entity(item_entity);
let player = Arc::clone(args.player);
let slot = args.equipment_slot.clone();
tokio::spawn(async move {
player.damage_item_in_slot(&slot, 1).await;
});
args.player.damage_item_in_slot(args.equipment_slot, 1);
BlockActionResult::Consume
}
}

View File

@@ -28,7 +28,11 @@ impl BlockBehaviour for DaylightDetectorBlock {
}
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 {
return BlockActionResult::Pass;
}

View File

@@ -669,7 +669,7 @@ impl DispenserBlock {
item: &mut ItemStack,
) {
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);
return;
};
@@ -717,7 +717,6 @@ impl DispenserBlock {
*ctx.position,
ctx.facing.to_block_direction(),
)
.await
};
if emptied {
@@ -737,7 +736,7 @@ impl DispenserBlock {
true
} else {
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);
},
ctx.world,
@@ -745,7 +744,6 @@ impl DispenserBlock {
front,
front_block,
)
.await
};
if ignited {
@@ -761,7 +759,7 @@ impl DispenserBlock {
let front = Self::target_position(ctx);
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);
Self::play_dispense_effects(ctx, WorldEvent::SoundDispenserDispense);
} else {

View File

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

View File

@@ -72,12 +72,7 @@ pub trait FlowingFluid: Send + Sync {
/// 3. Triggering fluid spread to adjacent positions
///
/// Sources (level 8, non-falling) always spread without state changes.
async fn on_scheduled_tick_internal(
&self,
world: &Arc<World>,
fluid: &Fluid,
block_pos: &BlockPos,
) {
fn on_scheduled_tick_internal(&self, world: &Arc<World>, fluid: &Fluid, block_pos: &BlockPos) {
let current_block_state_id = world.get_block_state_id(block_pos);
let block = Block::from_state_id(current_block_state_id);
@@ -96,7 +91,7 @@ pub trait FlowingFluid: Send + Sync {
// Update state if non-source
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 {
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
self.try_flow(world, fluid, block_pos, &state_for_spreading)
.await;
self.try_flow(world, fluid, block_pos, &state_for_spreading);
}
/// 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
///
/// Sources with 3+ adjacent sources also spread to sides when flowing down.
async fn try_flow(
fn try_flow(
&self,
world: &Arc<World>,
fluid: &Fluid,
@@ -153,24 +147,23 @@ pub trait FlowingFluid: Send + Sync {
// Try to flow down first
if is_hole {
let falling_props = self.get_flowing(fluid, Level::L8, true);
self.spread_to(world, fluid, &below_pos, falling_props.to_state_id(fluid))
.await;
self.spread_to(world, fluid, &below_pos, falling_props.to_state_id(fluid));
// Check if we should also spread to sides
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 {
self.flow_to_sides(world, fluid, block_pos, props).await;
self.flow_to_sides(world, fluid, block_pos, props);
}
}
return;
}
// 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,
world: &Arc<World>,
fluid: &Fluid,
@@ -205,7 +198,7 @@ pub trait FlowingFluid: Send + Sync {
///
/// # Returns
/// New fluid properties, or None if fluid should drain
async fn get_new_liquid(
fn get_new_liquid(
&self,
world: &Arc<World>,
fluid: &Fluid,
@@ -295,7 +288,7 @@ pub trait FlowingFluid: Send + Sync {
/// - Fluid tick scheduling for non-source blocks
///
/// Called by `spread_to` implementations after fluid-specific pre-checks.
async fn apply_spread(
fn apply_spread(
&self,
world: &Arc<World>,
fluid: &Fluid,
@@ -318,9 +311,7 @@ pub trait FlowingFluid: Send + Sync {
// Check for infinite source formation before quiescence checks
if !current_is_source && self.can_convert_to_source(world) {
let should_convert = self
.check_infinite_source_formation(world, fluid, pos)
.await;
let should_convert = self.check_infinite_source_formation(world, fluid, pos);
if should_convert {
let source_props = self.get_source(fluid, false);
@@ -355,7 +346,7 @@ pub trait FlowingFluid: Send + Sync {
&pumpkin_data::Block::WATER,
);
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 {
return;
@@ -365,9 +356,7 @@ pub trait FlowingFluid: Send + Sync {
// Check for infinite source formation after placing new fluid
if self.can_convert_to_source(world) {
let should_convert = self
.check_infinite_source_formation(world, fluid, pos)
.await;
let should_convert = self.check_infinite_source_formation(world, fluid, pos);
if should_convert {
let source_props = self.get_source(fluid, false);
@@ -396,7 +385,7 @@ pub trait FlowingFluid: Send + Sync {
///
/// # Returns
/// `true` if position should convert to a source block
async fn check_infinite_source_formation(
fn check_infinite_source_formation(
&self,
world: &Arc<World>,
fluid: &Fluid,
@@ -444,23 +433,16 @@ pub trait FlowingFluid: Send + Sync {
///
/// Default implementation delegates to `apply_spread`. Implementations like
/// lava can override to add fluid-specific logic (e.g., water -> stone conversion).
async fn spread_to(
&self,
world: &Arc<World>,
fluid: &Fluid,
pos: &BlockPos,
state_id: BlockStateId,
) {
fn spread_to(&self, world: &Arc<World>, fluid: &Fluid, pos: &BlockPos, state_id: BlockStateId) {
let new_props = FlowingFluidProperties::from_state_id(state_id, fluid);
self.apply_spread(world, fluid, pos, state_id, new_props)
.await;
self.apply_spread(world, fluid, pos, state_id, new_props);
}
/// Spreads fluid horizontally to adjacent positions using pathfinding.
///
/// Uses `get_spread` to find optimal flow directions (shortest distance to holes)
/// and the computed fluid state for each target position.
async fn flow_to_sides(
fn flow_to_sides(
&self,
world: &Arc<World>,
fluid: &Fluid,
@@ -479,12 +461,12 @@ pub trait FlowingFluid: Send + Sync {
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) {
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::{
Block, BlockDirection, BlockState, BlockStateId,
block_properties::blocks_movement,
damage::DamageType,
dimension::Dimension,
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) {
let world = world.clone();
let block_pos = *block_pos;
tokio::spawn(async move {
Self.on_scheduled_tick_internal(&world, &Fluid::FLOWING_LAVA, &block_pos)
.await;
});
Self.on_scheduled_tick_internal(world, &Fluid::FLOWING_LAVA, block_pos);
}
fn on_neighbor_update(
@@ -193,68 +187,58 @@ impl FluidBehaviour for FlowingLava {
}
fn random_tick(&self, _fluid: &Fluid, world: &Arc<World>, block_pos: &BlockPos) {
let world = world.clone();
let block_pos = *block_pos;
tokio::spawn(async move {
if !Self::can_spread_fire_around(&world, &block_pos) {
return;
}
if !Self::can_spread_fire_around(world, block_pos) {
return;
}
let passes = rand::random_range(0..3);
if passes > 0 {
let mut test_pos = block_pos;
let passes = rand::random_range(0..3);
if passes > 0 {
let mut test_pos = *block_pos;
for _ in 0..passes {
test_pos = test_pos.offset(Vector3::new(
rand::random_range(-1..=1),
1,
rand::random_range(-1..=1),
));
for _ in 0..passes {
test_pos = test_pos.offset(Vector3::new(
rand::random_range(-1..=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;
}
let Some(block_state) = world.get_block_state_if_loaded(&test_pos) else {
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 if block.is_solid() {
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
.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
}
async fn spread_to(
&self,
world: &Arc<World>,
fluid: &Fluid,
pos: &BlockPos,
state_id: BlockStateId,
) {
fn spread_to(&self, world: &Arc<World>, fluid: &Fluid, pos: &BlockPos, state_id: BlockStateId) {
let new_props = FlowingFluidProperties::from_state_id(state_id, fluid);
let current_state_id = world.get_block_state_id(pos);
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
self.apply_spread(world, fluid, pos, state_id, new_props)
.await;
self.apply_spread(world, fluid, pos, state_id, new_props);
}
}

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
/// - All directions with equal minimum distance are returned
/// - 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,
world: &Arc<World>,
fluid: &Fluid,
@@ -63,7 +63,7 @@ pub async fn get_spread<T: FlowingFluid + Sync + ?Sized>(
}
// 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;
};

View File

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

View File

@@ -401,7 +401,7 @@ pub struct BlockEvent {
pub data: u8,
}
pub async fn drop_loot(
pub fn drop_loot(
world: &Arc<World>,
block: &Block,
pos: &BlockPos,
@@ -419,7 +419,7 @@ pub async fn drop_loot(
cancelled: false,
};
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 {
for stack in event.items {
@@ -440,7 +440,7 @@ pub async fn drop_loot(
exp: amount,
};
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 {
ExperienceOrbEntity::spawn(world, pos.to_f64(), event.exp as u32);

View File

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

View File

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

View File

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

View File

@@ -87,7 +87,7 @@ impl CommandExecutor for LocationExecutor {
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),
);
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 {
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,
) -> Result<i32, CommandError> {
let mut nbt = NbtCompound::new();
entity.write_nbt(&mut nbt).await;
entity.write_nbt(&mut nbt);
let tag = NbtTag::Compound(nbt);
let result = get_i32_result(&tag)?;

View File

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

View File

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

View File

@@ -39,7 +39,7 @@ fn execute_as_modifier<'a>(
let mut sources = Vec::new();
for target in targets {
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();
source.entity = Some(target.clone());
source.name = name;

View File

@@ -62,7 +62,7 @@ impl Executor {
translation_key,
translation_key,
[
target.get_display_name().await,
target.get_display_name(),
TextComponent::text(val.to_string()),
],
))
@@ -126,20 +126,20 @@ impl Executor {
match self.exp_type {
ExpType::Levels => {
if self.mode == Mode::Add {
target.add_experience_levels(amount).await;
target.add_experience_levels(amount);
} else {
target.set_experience_level(amount, true).await;
target.set_experience_level(amount, true);
}
}
ExpType::Points => {
if self.mode == Mode::Add {
target.add_experience_points(amount).await;
target.add_experience_points(amount);
} else {
let current_lvl = target.experience_level.load(Ordering::Relaxed);
if amount > experience::points_in_level(current_lvl) {
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
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);
sender.send_message(msg).await;

View File

@@ -353,12 +353,12 @@ impl CommandExecutor for PrintForEntityExecutor {
&context.source,
&player.gameprofile,
translation::java::COMMANDS_FETCHPROFILE_ENTITY_SUCCESS,
player.get_display_name().await,
player.get_display_name(),
)
.await;
Ok(1)
} 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(
translation::java::COMMANDS_GAMEMODE_SUCCESS_OTHER,
translation::bedrock::COMMANDS_GAMEMODE_SUCCESS_OTHER,
[target.get_display_name().await, gamemode_comp],
[target.get_display_name(), gamemode_comp],
))
.await;
}

View File

@@ -73,7 +73,7 @@ impl CommandExecutor for Executor {
id: item_name.to_string().into(),
count: Some(item_count.min(99)),
}),
targets[0].get_display_name().await,
targets[0].get_display_name(),
],
)
} 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,
[
targets[0].get_display_name().await,
targets[0].get_display_name(),
TextComponent::text("[")
.add_child(item.translated_name())
.add_child(TextComponent::text("]"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -87,7 +87,7 @@ async fn rotate_entity(
/// Sends success message for the rotate command.
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
.send_message(TextComponent::translate_cross(
translation::java::COMMANDS_ROTATE_SUCCESS,

View File

@@ -35,7 +35,7 @@ impl CommandExecutor for StopSpectateExecutor {
};
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(
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 {
let display_name = player.get_display_name().await;
let display_name = player.get_display_name();
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
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 player_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(
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)
.await;
let target_name = target.get_display_name().await;
let target_name = target.get_display_name();
sender
.send_message(TextComponent::translate_cross(
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,
@@ -155,7 +155,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
// First validate all players
for player in players {
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(
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();
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(
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
@@ -201,7 +201,7 @@ impl CommandExecutor for SpectateTargetOtherExecutor {
succeeded += 1;
}
let target_name = target.get_display_name().await;
let target_name = target.get_display_name();
sender
.send_message(TextComponent::translate_cross(
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 name = entity.get_display_name().await;
let name = entity.get_display_name();
world.spawn_entity(entity);
sender
.send_message(TextComponent::translate_cross(

View File

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

View File

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

View File

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

View File

@@ -128,7 +128,7 @@ impl CommandExecutor for TargetPlayerExecutor {
"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(port.to_string()),
],

View File

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

View File

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

View File

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

View File

@@ -54,7 +54,7 @@ impl ServerPlayerData {
player.on_handled_screen_closed().await;
let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt).await;
player.write_nbt(&mut nbt);
let storage = self.storage.clone();
let uuid = player.gameprofile.id;
@@ -84,7 +84,7 @@ impl ServerPlayerData {
for world in server.worlds.load().iter() {
for player in world.players.load().iter() {
let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt).await;
player.write_nbt(&mut nbt);
let storage = self.storage.clone();
let uuid = player.gameprofile.id;
@@ -194,7 +194,7 @@ impl ServerPlayerData {
let uuid = player.gameprofile.id;
let mut nbt = NbtCompound::new();
player.write_nbt(&mut nbt).await;
player.write_nbt(&mut nbt);
let storage = self.storage.clone();
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::vector3::Vector3;
use pumpkin_world::{chunk::ChunkHeightmapType, world::BlockFlags};
use std::sync::Arc;
use std::sync::atomic::Ordering;
use tokio::sync::Mutex;
use std::sync::{Arc, Mutex};
use crate::entity::{
Entity, EntityBase,
@@ -284,31 +283,43 @@ impl EnderDragonEntity {
}
}
pub async fn set_phase(&self, phase_type: EnderDragonPhase) {
let mut phase_lock = self.phase.lock().await;
pub fn set_phase(&self, phase_type: EnderDragonPhase) {
let mut phase_lock = self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if *phase_lock == phase_type {
return;
}
let old_phase = self.phase_manager.get_phase(*phase_lock);
old_phase.end(self).await;
old_phase.end(self);
*phase_lock = 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) {
let mut initialized = self.nodes_initialized.lock().await;
fn ensure_nodes_initialized(&self) {
let mut initialized = self
.nodes_initialized
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if *initialized {
return;
}
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 {
let mut y_adjustment = 5;
let node_x;
@@ -346,19 +357,28 @@ impl EnderDragonEntity {
let new_path = find_path(&nodes, nearest, dest, None);
drop(nodes);
*self.target_node.lock().await = dest;
*self.path.lock().await = new_path;
*self
.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;
}
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();
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 {
self.ensure_nodes_initialized().await;
let nodes = self.nodes.lock().await;
pub fn find_closest_node_to(&self, pos: Vector3<f64>) -> usize {
self.ensure_nodes_initialized();
let nodes = self
.nodes
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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)
}
pub async fn steer_toward(
pub fn steer_toward(
&self,
pos: Vector3<f64>,
target: Vector3<f64>,
@@ -459,7 +479,10 @@ impl EnderDragonEntity {
}
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 += y_rot_d * turn_speed;
entity.yaw.store(yaw + *y_rot_a * 0.1);
@@ -485,11 +508,24 @@ impl EnderDragonEntity {
));
}
async fn update_flap_time(&self) {
let sitting = self.phase.lock().await.is_sitting();
let in_wall = *self.in_wall.lock().await;
let mut flap = self.flap_time.lock().await;
let mut o_flap = self.o_flap_time.lock().await;
fn update_flap_time(&self) {
let sitting = self
.phase
.lock()
.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;
*flap += if sitting {
@@ -504,11 +540,19 @@ impl EnderDragonEntity {
}
}
async fn tick_growl(&self) {
if self.phase.lock().await.is_sitting() {
fn tick_growl(&self) {
if self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.is_sitting()
{
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 {
*t -= 1;
} else {
@@ -537,7 +581,7 @@ impl EnderDragonEntity {
.cloned()
}
async fn handle_player_collisions(&self) {
fn handle_player_collisions(&self) {
let world = self.mob_entity.living_entity.entity.world.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);
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);
}
}
@@ -596,8 +645,11 @@ impl EnderDragonEntity {
}
}
async fn tick_block_breaking(&self) {
let phase_type = *self.phase.lock().await;
fn tick_block_breaking(&self) {
let phase_type = *self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if phase_type.is_sitting() || phase_type == EnderDragonPhase::Dying {
return;
}
@@ -622,9 +674,11 @@ impl EnderDragonEntity {
}
}
async fn tick_parts(&self) {
let history: tokio::sync::MutexGuard<'_, DragonFlightHistory> =
self.flight_history.lock().await;
fn tick_parts(&self) {
let history = self
.flight_history
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let p5 = history.get(5);
let p10 = history.get(10);
let p0 = history.get(0);
@@ -657,13 +711,21 @@ impl EnderDragonEntity {
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
} else {
(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 ss2 = rot2.sin() 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.ensure_nodes_initialized().await;
self.update_flap_time().await;
self.ensure_nodes_initialized();
self.update_flap_time();
{
let y = self.mob_entity.living_entity.entity.pos.load().y;
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();
{
@@ -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);
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 {
self.handle_player_collisions().await;
self.tick_block_breaking().await;
self.handle_player_collisions();
self.tick_block_breaking();
}
phase.tick(self).await;
phase.tick(self);
if phase_type == EnderDragonPhase::Dying {
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 {
let pos = self.mob_entity.living_entity.entity.pos.load();
self.steer_toward(
@@ -750,18 +824,23 @@ impl EnderDragonEntity {
phase.get_fly_speed(),
phase.get_turn_speed(),
0.5,
)
.await;
);
}
self.mob_entity.living_entity.entity.send_pos_rot();
self.tick_parts().await;
self.tick_parts();
}
pub async fn hurt(&self, damage: f32) {
let phase_type: EnderDragonPhase = *self.phase.lock().await;
pub fn hurt(&self, damage: f32) {
let phase_type: EnderDragonPhase = *self
.phase
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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>) {
let entity_id = self.mob_entity.living_entity.entity.entity_id;
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;
});
self.ai_step();
}
fn on_damage(&self, _damage_type: DamageType, _source: Option<&dyn EntityBase>) {
let living = &self.mob_entity.living_entity;
if living.health.load() <= 0.0 {
let world = self.mob_entity.living_entity.entity.world.load_full();
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;
}
});
self.set_phase(EnderDragonPhase::Dying);
}
}

View File

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

View File

@@ -1,6 +1,5 @@
use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext, find_path};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3;
use std::sync::atomic::Ordering;
@@ -11,84 +10,104 @@ impl super::Phase for CirclingPhase {
EnderDragonPhase::Circling
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let pos = dragon.mob_entity.living_entity.entity.pos.load();
let mut target_location = dragon.target_location.lock().await;
fn tick(&self, dragon: &EnderDragonEntity) {
let pos = dragon.mob_entity.living_entity.entity.pos.load();
let mut target_location = dragon
.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()
|| !(100.0..=22500.0).contains(&d0)
|| dragon
.mob_entity
.living_entity
.entity
.horizontal_collision
.load(Ordering::Relaxed)
{
let mut path = dragon.path.lock().await;
if path.is_empty() {
drop(path);
let i = dragon.find_closest_node().await;
let mut j = i as i32;
if target_location.is_none()
|| !(100.0..=22500.0).contains(&d0)
|| dragon
.mob_entity
.living_entity
.entity
.horizontal_collision
.load(Ordering::Relaxed)
{
let mut path = dragon
.path
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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;
if rand::random_range(0..8) == 0 {
*clockwise = !*clockwise;
j = i as i32 + 6;
}
if *clockwise {
j += 1;
} 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;
let mut clockwise = dragon
.holding_pattern_clockwise
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if rand::random_range(0..8) == 0 {
*clockwise = !*clockwise;
j = i as i32 + 6;
}
if *clockwise {
j += 1;
} else {
j -= 1;
}
drop(clockwise);
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));
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()
.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_bool(0.5) {
dragon.set_phase(EnderDragonPhase::FlyToPortal).await;
} else if let Some(player) = dragon.find_nearest_player() {
*dragon.target_player.lock().await = Some(player.gameprofile.id);
dragon.set_phase(EnderDragonPhase::Strafing).await;
}
if rand::random_range(0..64) == 0 {
if rand::random_bool(0.5) {
dragon.set_phase(EnderDragonPhase::FlyToPortal);
} else if let Some(player) = dragon.find_nearest_player() {
*dragon
.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 crate::entity::boss::ender_dragon::{DEATH_TIMER_MAX, EnderDragonEntity};
use crate::entity::experience_orb::ExperienceOrbEntity;
use futures::future::BoxFuture;
use pumpkin_data::particle::Particle;
use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_util::math::vector3::Vector3;
@@ -13,85 +12,79 @@ impl super::Phase for DyingPhase {
EnderDragonPhase::Dying
}
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
*dragon.target_location.lock().await = None;
})
fn begin(&self, dragon: &EnderDragonEntity) {
*dragon
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let mut t = dragon.dragon_death_time.lock().await;
*t += 1;
fn tick(&self, dragon: &EnderDragonEntity) {
let mut t = dragon
.dragon_death_time
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*t += 1;
let entity = &dragon.mob_entity.living_entity.entity;
let world = entity.world.load();
let entity = &dragon.mob_entity.living_entity.entity;
let world = entity.world.load();
if *t == 1 {
world.play_sound(
Sound::EntityEnderDragonDeath,
SoundCategory::Hostile,
&entity.pos.load(),
);
}
if *t == 1 {
world.play_sound(
Sound::EntityEnderDragonDeath,
SoundCategory::Hostile,
&entity.pos.load(),
);
}
if *t >= 180 && *t <= 200 {
let xo = (rand::random::<f32>() - 0.5) * 8.0;
let yo = (rand::random::<f32>() - 0.5) * 4.0;
let zo = (rand::random::<f32>() - 0.5) * 8.0;
let pos = entity.pos.load();
world.spawn_particle(
Vector3::new(
pos.x + xo as f64,
pos.y + 2.0 + yo as f64,
pos.z + zo as f64,
),
Vector3::new(0.0, 0.0, 0.0),
0.0,
1,
Particle::ExplosionEmitter,
);
}
if *t >= 180 && *t <= 200 {
let xo = (rand::random::<f32>() - 0.5) * 8.0;
let yo = (rand::random::<f32>() - 0.5) * 4.0;
let zo = (rand::random::<f32>() - 0.5) * 8.0;
let pos = entity.pos.load();
world.spawn_particle(
Vector3::new(
pos.x + xo as f64,
pos.y + 2.0 + yo as f64,
pos.z + zo as f64,
),
Vector3::new(0.0, 0.0, 0.0),
0.0,
1,
Particle::ExplosionEmitter,
);
}
let xp_count = if let Some(ref fight_mutex) = world.dragon_fight
&& !fight_mutex
let xp_count = if let Some(ref fight_mutex) = world.dragon_fight
&& !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()
.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,
);
.set_dragon_killed(&world, entity.entity_uuid);
}
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()
.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();
for part in &dragon.parts {
part.entity.remove();
}
})
entity.remove();
}
}
}

View File

@@ -1,6 +1,5 @@
use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3;
pub struct FlyToPortalPhase;
@@ -10,21 +9,25 @@ impl super::Phase for FlyToPortalPhase {
EnderDragonPhase::FlyToPortal
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let origin = {
let guard = dragon.fight_origin.lock().await;
guard.0
};
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();
fn tick(&self, dragon: &EnderDragonEntity) {
let origin = {
let guard = dragon
.fight_origin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.0
};
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 {
dragon.set_phase(EnderDragonPhase::LandingApproach).await;
return;
}
if pos.distance_squared(target) < 4.0 {
dragon.set_phase(EnderDragonPhase::LandingApproach);
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 crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3;
pub struct HoveringPhase;
@@ -10,20 +9,24 @@ impl super::Phase for HoveringPhase {
EnderDragonPhase::Hovering
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let origin = {
let guard = dragon.fight_origin.lock().await;
guard.0
};
let target = Vector3::new(origin.x as f64, NODE_Y as f64 + 10.0, origin.z as f64);
fn tick(&self, dragon: &EnderDragonEntity) {
let origin = {
let guard = dragon
.fight_origin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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) {
dragon.set_phase(EnderDragonPhase::TakingOff).await;
return;
}
if rand::random_bool(0.01) {
dragon.set_phase(EnderDragonPhase::TakingOff);
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 crate::entity::boss::ender_dragon::EnderDragonEntity;
use futures::future::BoxFuture;
pub struct LandingPhase;
@@ -9,17 +8,22 @@ impl super::Phase for LandingPhase {
EnderDragonPhase::Landing
}
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
*dragon.target_location.lock().await = None;
})
fn begin(&self, dragon: &EnderDragonEntity) {
*dragon
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
dragon.set_phase(EnderDragonPhase::SitAttacking).await;
*dragon.ticks_sitting.lock().await = 0;
*dragon.sit_attack_timer.lock().await = 0;
})
fn tick(&self, dragon: &EnderDragonEntity) {
dragon.set_phase(EnderDragonPhase::SitAttacking);
*dragon
.ticks_sitting
.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 crate::entity::boss::ender_dragon::{EnderDragonEntity, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3;
pub struct LandingApproachPhase;
@@ -10,21 +9,25 @@ impl super::Phase for LandingApproachPhase {
EnderDragonPhase::LandingApproach
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let origin = {
let guard = dragon.fight_origin.lock().await;
guard.0
};
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();
fn tick(&self, dragon: &EnderDragonEntity) {
let origin = {
let guard = dragon
.fight_origin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.0
};
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 {
dragon.set_phase(EnderDragonPhase::Landing).await;
return;
}
if pos.distance_squared(target) < 1.0 {
dragon.set_phase(EnderDragonPhase::Landing);
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 futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3;
use std::sync::Arc;
@@ -29,13 +28,9 @@ pub use taking_off::TakingOffPhase;
pub trait Phase: Send + Sync {
fn get_type(&self) -> EnderDragonPhase;
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()>;
fn begin<'a>(&'a self, _dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn end<'a>(&'a self, _dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async {})
}
fn tick(&self, dragon: &EnderDragonEntity);
fn begin(&self, _dragon: &EnderDragonEntity) {}
fn end(&self, _dragon: &EnderDragonEntity) {}
fn is_sitting(&self) -> bool {
false
}

View File

@@ -1,6 +1,5 @@
use super::EnderDragonPhase;
use crate::entity::boss::ender_dragon::EnderDragonEntity;
use futures::future::BoxFuture;
pub struct SitAttackingPhase;
@@ -9,39 +8,51 @@ impl super::Phase for SitAttackingPhase {
EnderDragonPhase::SitAttacking
}
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
*dragon.target_location.lock().await = None;
})
fn begin(&self, dragon: &EnderDragonEntity) {
*dragon
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let mut timer = dragon.sit_attack_timer.lock().await;
*timer += 1;
fn tick(&self, dragon: &EnderDragonEntity) {
let mut timer = dragon
.sit_attack_timer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*timer += 1;
if *timer > 40 {
*timer = 0;
let should_breathe = rand::random_bool(0.5);
let should_take_off = *dragon.ticks_sitting.lock().await > 200;
drop(timer);
if *timer > 40 {
*timer = 0;
let should_breathe = rand::random_bool(0.5);
let should_take_off = *dragon
.ticks_sitting
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
> 200;
drop(timer);
if should_breathe {
dragon.set_phase(EnderDragonPhase::SitBreathing).await;
*dragon.breathing_timer.lock().await = 0;
} else if should_take_off {
dragon.set_phase(EnderDragonPhase::TakingOff).await;
}
} else {
drop(timer);
if should_breathe {
dragon.set_phase(EnderDragonPhase::SitBreathing);
*dragon
.breathing_timer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = 0;
} else if should_take_off {
dragon.set_phase(EnderDragonPhase::TakingOff);
}
} else {
drop(timer);
}
let mut dmg = dragon.sitting_damage_received.lock().await;
if *dmg > 150.0 {
*dmg = 0.0;
drop(dmg);
dragon.set_phase(EnderDragonPhase::TakingOff).await;
}
})
let mut dmg = dragon
.sitting_damage_received
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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::{
Entity, area_effect_cloud::AreaEffectCloudEntity, boss::ender_dragon::EnderDragonEntity,
};
use futures::future::BoxFuture;
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3;
@@ -13,61 +12,66 @@ impl super::Phase for SitBreathingPhase {
EnderDragonPhase::SitBreathing
}
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
*dragon.target_location.lock().await = None;
})
fn begin(&self, dragon: &EnderDragonEntity) {
*dragon
.target_location
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let mut timer = dragon.breathing_timer.lock().await;
*timer += 1;
fn tick(&self, dragon: &EnderDragonEntity) {
let mut timer = dragon
.breathing_timer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*timer += 1;
if *timer > 100 {
*timer = 0;
drop(timer);
dragon.set_phase(EnderDragonPhase::SitAttacking).await;
return;
}
if *timer > 100 {
*timer = 0;
drop(timer);
dragon.set_phase(EnderDragonPhase::SitAttacking);
return;
}
drop(timer);
let timer_val = *dragon.breathing_timer.lock().await;
if timer_val == 1 {
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();
let timer_val = *dragon
.breathing_timer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if timer_val == 1 {
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
let offset = Vector3::new(-yaw.sin() * 2.0, 0.5, yaw.cos() * 2.0);
let cloud_pos = pos.add(&offset);
// 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 cloud_pos = pos.add(&offset);
let cloud_entity =
Entity::new(world.clone(), cloud_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, // duration
3.0, // radius
20, // reapplication delay
20, // wait time
0.5, // radius on use
-100, // duration on use
);
world.spawn_entity(cloud);
}
})
let cloud_entity =
Entity::new(world.clone(), cloud_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, // duration
3.0, // radius
20, // reapplication delay
20, // wait time
0.5, // radius on use
-100, // duration on use
);
world.spawn_entity(cloud);
}
}
}

View File

@@ -5,7 +5,6 @@ use crate::entity::{
area_effect_cloud::AreaEffectCloudEntity,
boss::ender_dragon::{EnderDragonEntity, Vector3Ext, find_path},
};
use futures::future::BoxFuture;
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3;
use std::sync::atomic::Ordering;
@@ -17,153 +16,182 @@ impl super::Phase for StrafingPhase {
EnderDragonPhase::Strafing
}
fn begin<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
*dragon.fireball_charge.lock().await = 0;
*dragon.target_location.lock().await = None;
})
fn begin(&self, dragon: &EnderDragonEntity) {
*dragon
.fireball_charge
.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)]
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let target_id = *dragon.target_player.lock().await;
let world = dragon.mob_entity.living_entity.entity.world.load();
let pos = dragon.mob_entity.living_entity.entity.pos.load();
fn tick(&self, dragon: &EnderDragonEntity) {
let target_id = *dragon
.target_player
.lock()
.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 {
world
.players
.load()
.iter()
.find(|p| p.gameprofile.id == id)
.cloned()
} else {
None
};
let player_target = if let Some(id) = target_id {
world
.players
.load()
.iter()
.find(|p| p.gameprofile.id == id)
.cloned()
} else {
None
};
let Some(player) = player_target else {
dragon.set_phase(EnderDragonPhase::Circling).await;
return;
};
let Some(player) = player_target else {
dragon.set_phase(EnderDragonPhase::Circling);
return;
};
let player_pos = player.get_entity().pos.load();
let mut path = dragon.path.lock().await;
let mut target_location = dragon.target_location.lock().await;
let player_pos = player.get_entity().pos.load();
let mut path = dragon
.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() {
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));
drop(path);
let i = dragon.find_closest_node();
let j = dragon.find_closest_node_to(player_pos);
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;
}
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() {
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));
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(path);
drop(target_location);
}
drop(path);
drop(target_location);
if player_pos.distance_squared(pos) < 4096.0 {
let mut charge = dragon.fireball_charge.lock().await;
if player_pos.distance_squared(pos) < 4096.0 {
let mut charge = dragon
.fireball_charge
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let aim_diff = player_pos - pos;
let aim = if aim_diff.length_squared() > 1e-6 {
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;
}
let aim_diff = player_pos - pos;
let aim = if aim_diff.length_squared() > 1e-6 {
aim_diff.normalize()
} else {
let mut charge = dragon.fireball_charge.lock().await;
if *charge > 0 {
*charge -= 1;
}
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()
.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 crate::entity::boss::ender_dragon::{EnderDragonEntity, NODE_Y, Vector3Ext};
use futures::future::BoxFuture;
use pumpkin_util::math::vector3::Vector3;
pub struct TakingOffPhase;
@@ -10,21 +9,25 @@ impl super::Phase for TakingOffPhase {
EnderDragonPhase::TakingOff
}
fn tick<'a>(&'a self, dragon: &'a EnderDragonEntity) -> BoxFuture<'a, ()> {
Box::pin(async move {
let origin = {
let guard = dragon.fight_origin.lock().await;
guard.0
};
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();
fn tick(&self, dragon: &EnderDragonEntity) {
let origin = {
let guard = dragon
.fight_origin
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard.0
};
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 {
dragon.set_phase(EnderDragonPhase::Circling).await;
return;
}
if pos.distance_squared(target) < 16.0 {
dragon.set_phase(EnderDragonPhase::Circling);
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::{
Arc, Weak,
Arc, Mutex, Weak,
atomic::{AtomicBool, AtomicI32, Ordering},
};
use tokio::sync::Mutex;
use uuid::Uuid;
use pumpkin_data::{
@@ -26,7 +25,7 @@ use pumpkin_world::world::BlockFlags;
use crate::{
entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::goal::{
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal,
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 tracking_radius_sq = 50.0 * 50.0;
let players = world.players.load();
@@ -273,7 +272,10 @@ impl WitherEntity {
.map(|p| p.gameprofile.id)
.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 {
if !bossbar_players.contains(&uid) {
@@ -306,8 +308,11 @@ impl WitherEntity {
}
}
async fn remove_all_bossbar(&self, world: &Arc<crate::world::World>) {
let mut bossbar_players = self.bossbar_players.lock().await;
fn remove_all_bossbar(&self, world: &Arc<crate::world::World>) {
let mut bossbar_players = self
.bossbar_players
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let players = world.players.load();
for player in players.iter() {
if bossbar_players.contains(&player.gameprofile.id) {
@@ -318,18 +323,18 @@ impl WitherEntity {
}
#[expect(clippy::too_many_lines)]
async fn async_mob_tick(&self) {
fn tick_wither(&self) {
let entity = &self.mob_entity.living_entity.entity;
let world = entity.world.load();
if world.level_info.load().difficulty == Difficulty::Peaceful {
self.remove_all_bossbar(&world).await;
self.remove_all_bossbar(&world);
entity.remove();
return;
}
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) {
let pos = entity.block_pos.load();
world.drop_stack(&pos, ItemStack::new(1, &Item::NETHER_STAR));
@@ -343,18 +348,16 @@ impl WitherEntity {
if invul > 0 {
let new_count = invul - 1;
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 {
let pos = entity.pos.load();
let eye_y = pos.y + entity.get_eye_height();
world
.explode(
Vector3::new(pos.x, eye_y, pos.z),
7.0,
ExplosionInteraction::Mob,
)
.await;
world.explode(
Vector3::new(pos.x, eye_y, pos.z),
7.0,
ExplosionInteraction::Mob,
);
if !entity.silent.load(Ordering::Relaxed) {
world.sync_world_event(
@@ -378,7 +381,7 @@ impl WitherEntity {
} else {
0.0
};
self.update_bossbar(&world, progress).await;
self.update_bossbar(&world, progress);
if tick_count % 20 == 0 {
living.heal(1.0);
@@ -604,17 +607,7 @@ impl Mob for WitherEntity {
}
fn mob_tick<'a>(&'a self, _caller: &'a Arc<dyn EntityBase>) {
let entity_id = self.mob_entity.living_entity.entity.entity_id;
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;
});
self.tick_wither();
}
fn pre_damage(&self, damage_type: DamageType, source: Option<&dyn EntityBase>) -> bool {
@@ -662,35 +655,22 @@ impl Mob for WitherEntity {
fn post_tick(&self) {
let entity = &self.mob_entity.living_entity.entity;
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();
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.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));
}
});
self.remove_all_bossbar(&world);
if !self.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, ()> {
Box::pin(async move {
nbt.put_int("Invul", self.get_invulnerable_ticks());
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_int("Invul", self.get_invulnerable_ticks());
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
if let Some(invul) = nbt.get_int("Invul") {
self.set_invulnerable_ticks(invul);
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
if let Some(invul) = nbt.get_int("Invul") {
self.set_invulnerable_ticks(invul);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 pumpkin_data::entity::EntityType;
use pumpkin_data::item_stack::ItemStack;
@@ -135,66 +135,60 @@ impl EntityBase for LeashKnotEntity {
}
}
fn interact<'a>(
&'a self,
player: &'a Arc<Player>,
_item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move {
let world = player.world();
let knot_id = self.entity.entity_id;
let player_id = player.entity_id();
fn interact(&self, player: &Arc<Player>, _item_stack: &mut ItemStack) -> bool {
let world = player.world();
let knot_id = self.entity.entity_id;
let player_id = player.entity_id();
let search_dim = EntityDimensions {
width: 32.0,
height: 32.0,
eye_height: 16.0,
};
let pos = self.entity.pos.load();
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 search_dim = EntityDimensions {
width: 32.0,
height: 32.0,
eye_height: 16.0,
};
let pos = self.entity.pos.load();
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 mut attached_mob = false;
let mut player_leashed_mobs = Vec::new();
let mut attached_mob = false;
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 {
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
&& 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) {
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 {
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
}
})
if attached_mob || any_dropped {
self.play_placement_sound(&world);
true
} else {
false
}
}
fn cast_any(&self) -> &dyn std::any::Any {
self

View File

@@ -1,7 +1,7 @@
use core::f32;
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_nbt::compound::NbtCompound;
use pumpkin_util::math::vector3::Vector3;
@@ -17,17 +17,13 @@ impl PaintingEntity {
}
impl EntityBase for PaintingEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
nbt.put_byte("facing", self.entity.data.load(Ordering::Relaxed) as i8);
})
fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_byte("facing", self.entity.data.load(Ordering::Relaxed) as i8);
}
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
let facing = nbt.get_byte("facing").unwrap_or(3);
self.entity.data.store(facing as i32, Ordering::Relaxed);
})
fn read_custom_nbt(&self, nbt: &NbtCompound) {
let facing = nbt.get_byte("facing").unwrap_or(3);
self.entity.data.store(facing as i32, Ordering::Relaxed);
}
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;
pub struct HungerMobEffect;
@@ -11,19 +11,13 @@ impl MobEffect for HungerMobEffect {
(duration as u32).is_multiple_of(20)
}
fn apply_effect_tick<'a>(
&'a self,
living: &'a LivingEntity,
amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let world = living.entity.world.load();
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);
}
})
fn apply_effect_tick(&self, living: &LivingEntity, amplifier: u8) {
let world = living.entity.world.load();
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_util::math::vector3::Vector3;
use crate::entity::effect::{EffectFuture, MobEffect};
use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity;
use crate::entity::r#type::from_type;
pub struct InfestedMobEffect;
impl MobEffect for InfestedMobEffect {
fn on_mob_hurt<'a>(
&'a self,
living: &'a LivingEntity,
fn on_mob_hurt(
&self,
living: &LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
_damage_type: &DamageType,
_damage_amount: f32,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
// Wither, ender dragon and silverfish are immune
if living.entity.entity_type == &EntityType::WITHER
|| living.entity.entity_type == &EntityType::ENDER_DRAGON
|| living.entity.entity_type == &EntityType::SILVERFISH
{
return;
) {
// Wither, ender dragon and silverfish are immune
if living.entity.entity_type == &EntityType::WITHER
|| living.entity.entity_type == &EntityType::ENDER_DRAGON
|| living.entity.entity_type == &EntityType::SILVERFISH
{
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();
// 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);
}
}
})
world.play_sound(Sound::EntitySilverfishHurt, SoundCategory::Hostile, &center);
}
}
}

View File

@@ -9,9 +9,6 @@ pub mod weaving;
pub mod wind_charged;
pub mod wither;
use std::future::Future;
use std::pin::Pin;
use pumpkin_data::damage::DamageType;
use pumpkin_data::effect::StatusEffect;
use pumpkin_nbt::compound::NbtCompound;
@@ -19,9 +16,7 @@ use pumpkin_nbt::tag::NbtTag;
use tracing::warn;
use crate::entity::living::LivingEntity;
use crate::entity::{NBTInitFuture, NBTStorage, NBTStorageInit, NbtFuture};
pub type EffectFuture<'a, T = ()> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
use crate::entity::{NBTStorage, NBTStorageInit};
pub trait MobEffect: Send + Sync {
/// 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.
fn apply_effect_tick<'a>(
&'a self,
_living: &'a LivingEntity,
_amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {})
}
fn apply_effect_tick(&self, _living: &LivingEntity, _amplifier: u8) {}
/// Called when an entity carrying this effect is hurt.
fn on_mob_hurt<'a>(
&'a self,
_living: &'a LivingEntity,
fn on_mob_hurt(
&self,
_living: &LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
_damage_type: &DamageType,
_damage_amount: f32,
) -> EffectFuture<'a, ()> {
Box::pin(async move {})
) {
}
/// Called when an entity carrying this effect dies.
fn on_mob_death<'a>(
&'a self,
_living: &'a LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
) -> EffectFuture<'a, ()> {
Box::pin(async move {})
}
fn on_mob_death(&self, _living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {}
}
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 {
fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
nbt.put("id", self.effect_type.minecraft_name);
if self.amplifier > 0 {
nbt.put("amplifier", NbtTag::Int(i32::from(self.amplifier)));
}
nbt.put("duration", NbtTag::Int(self.duration));
if self.ambient {
nbt.put("ambient", NbtTag::Byte(1));
}
if !self.show_particles {
nbt.put("show_particles", NbtTag::Byte(0));
}
let show_icon: i8 = i8::from(self.show_icon);
nbt.put("show_icon", NbtTag::Byte(show_icon));
})
fn write_nbt(&self, nbt: &mut NbtCompound) {
nbt.put("id", self.effect_type.minecraft_name);
if self.amplifier > 0 {
nbt.put("amplifier", NbtTag::Int(i32::from(self.amplifier)));
}
nbt.put("duration", NbtTag::Int(self.duration));
if self.ambient {
nbt.put("ambient", NbtTag::Byte(1));
}
if !self.show_particles {
nbt.put("show_particles", NbtTag::Byte(0));
}
let show_icon: i8 = i8::from(self.show_icon);
nbt.put("show_icon", NbtTag::Byte(show_icon));
}
}
impl NBTStorageInit for pumpkin_data::potion::Effect {
fn create_from_nbt<'a>(nbt: &'a mut NbtCompound) -> NBTInitFuture<'a, Self>
where
Self: 'a,
{
Box::pin(async move {
let Some(effect_id) = nbt.get_string("id") else {
warn!("Unable to read effect. Effect id is not present");
return None;
};
let Some(effect_type) = StatusEffect::from_minecraft_name(effect_id) else {
warn!("Unable to read effect. Unknown effect type: {effect_id}");
return None;
};
let Some(show_icon) = nbt.get_byte("show_icon") else {
warn!("Unable to read effect. Show icon is not present");
return None;
};
let amplifier = nbt.get_int("amplifier").unwrap_or(0) as u8;
let duration = nbt.get_int("duration").unwrap_or(0);
let ambient = nbt.get_byte("ambient").unwrap_or(0) == 1;
let show_particles = nbt.get_byte("show_particles").unwrap_or(1) == 1;
let show_icon = show_icon == 1;
Some(Self {
effect_type,
duration,
amplifier,
ambient,
show_particles,
show_icon,
blend: false,
})
fn create_from_nbt(nbt: &mut NbtCompound) -> Option<Self> {
let Some(effect_id) = nbt.get_string("id") else {
warn!("Unable to read effect. Effect id is not present");
return None;
};
let Some(effect_type) = StatusEffect::from_minecraft_name(effect_id) else {
warn!("Unable to read effect. Unknown effect type: {effect_id}");
return None;
};
let Some(show_icon) = nbt.get_byte("show_icon") else {
warn!("Unable to read effect. Show icon is not present");
return None;
};
let amplifier = nbt.get_int("amplifier").unwrap_or(0) as u8;
let duration = nbt.get_int("duration").unwrap_or(0);
let ambient = nbt.get_byte("ambient").unwrap_or(0) == 1;
let show_particles = nbt.get_byte("show_particles").unwrap_or(1) == 1;
let show_icon = show_icon == 1;
Some(Self {
effect_type,
duration,
amplifier,
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_util::math::vector3::Vector3;
use crate::entity::effect::{EffectFuture, MobEffect};
use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity;
use crate::entity::mob::slime::SlimeEntity;
use crate::entity::r#type::from_type;
@@ -12,36 +12,29 @@ use crate::entity::r#type::from_type;
pub struct OozingMobEffect;
impl MobEffect for OozingMobEffect {
fn on_mob_death<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
// Slimes are immune
if living.entity.entity_type == &EntityType::SLIME {
return;
fn on_mob_death(&self, living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {
// Slimes are immune
if living.entity.entity_type == &EntityType::SLIME {
return;
}
let world = living.entity.world.load();
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);
}
let world = living.entity.world.load();
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);
}
})
world.spawn_entity(entity_arc);
}
}
}

View File

@@ -1,6 +1,6 @@
use pumpkin_data::damage::DamageType;
use crate::entity::effect::{EffectFuture, MobEffect};
use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity;
pub struct PoisonMobEffect;
@@ -18,25 +18,19 @@ impl MobEffect for PoisonMobEffect {
}
}
fn apply_effect_tick<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let current_health = living.health.load();
if current_health > 1.0
&& let Some(dyn_self) = living
.entity
.world
.load()
.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);
}
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
let current_health = living.health.load();
if current_health > 1.0
&& let Some(dyn_self) = living
.entity
.world
.load()
.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;
pub struct RaidOmenMobEffect;
@@ -8,27 +8,21 @@ impl MobEffect for RaidOmenMobEffect {
duration == 1
}
fn apply_effect_tick<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let world = living.entity.world.load();
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
&& let Some(player) = entity.get_player()
&& !player.is_spectator()
{
let raid_pos = player
.get_raid_omen_position()
.unwrap_or_else(|| living.entity.block_pos.load());
let mut raids = world
.raids
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
raids.create_or_extend_raid(player, raid_pos, &world);
player.clear_raid_omen_position();
}
})
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
let world = living.entity.world.load();
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
&& let Some(player) = entity.get_player()
&& !player.is_spectator()
{
let raid_pos = player
.get_raid_omen_position()
.unwrap_or_else(|| living.entity.block_pos.load());
let mut raids = world
.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;
pub struct RegenerationMobEffect;
@@ -16,17 +16,11 @@ impl MobEffect for RegenerationMobEffect {
}
}
fn apply_effect_tick<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
) -> EffectFuture<'a, ()> {
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);
}
})
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
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;
pub struct SaturationMobEffect;
@@ -8,22 +8,16 @@ impl MobEffect for SaturationMobEffect {
true
}
fn apply_effect_tick<'a>(
&'a self,
living: &'a LivingEntity,
amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let world = living.entity.world.load();
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
&& let Some(player) = entity.get_player()
{
let hunger = amplifier + 1;
player.hunger_manager.add_hunger(hunger);
player
.hunger_manager
.add_saturation(f32::from(hunger) * 2.0);
}
})
fn apply_effect_tick(&self, living: &LivingEntity, amplifier: u8) {
let world = living.entity.world.load();
if let Some(entity) = world.get_entity_by_id(living.entity.entity_id)
&& let Some(player) = entity.get_player()
{
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_world::world::BlockFlags;
use crate::entity::effect::{EffectFuture, MobEffect};
use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity;
pub struct WeavingMobEffect;
impl MobEffect for WeavingMobEffect {
fn on_mob_death<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let world = living.entity.world.load();
fn on_mob_death(&self, living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {
let world = living.entity.world.load();
// Check if mob griefing is enabled or if player
let mob_griefing = world.level_info.load().game_rules.mob_griefing;
if !living.is_player() && !mob_griefing {
return;
// Check if mob griefing is enabled or if player
let mob_griefing = world.level_info.load().game_rules.mob_griefing;
if !living.is_player() && !mob_griefing {
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();
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;
}
}
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);
}
})
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_util::math::vector3::Vector3;
use crate::entity::effect::{EffectFuture, MobEffect};
use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity;
use crate::entity::projectile::wind_charge::BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR;
use crate::world::explosion::ExplosionInteraction;
@@ -10,35 +10,26 @@ use crate::world::explosion::ExplosionInteraction;
pub struct WindChargedMobEffect;
impl MobEffect for WindChargedMobEffect {
fn on_mob_death<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
_damage_type: &'a DamageType,
) -> 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);
fn on_mob_death(&self, living: &LivingEntity, _amplifier: u8, _damage_type: &DamageType) {
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
let gust_strength = 3.0 + rand::random::<f32>() * 2.0;
// gustStrength = 3.0 + random * 2.0
let gust_strength = 3.0 + rand::random::<f32>() * 2.0;
world
.explode_with_calculator(
center,
gust_strength,
ExplosionInteraction::Trigger,
Some(BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR.clone()),
)
.await;
world.explode_with_calculator(
center,
gust_strength,
ExplosionInteraction::Trigger,
Some(BREEZE_WIND_CHARGE_EXPLOSION_DAMAGE_CALCULATOR.clone()),
);
world.play_sound(
Sound::EntityBreezeWindBurst,
SoundCategory::Neutral,
&center,
);
})
world.play_sound(
Sound::EntityBreezeWindBurst,
SoundCategory::Hostile,
&center,
);
}
}

View File

@@ -1,6 +1,6 @@
use pumpkin_data::damage::DamageType;
use crate::entity::effect::{EffectFuture, MobEffect};
use crate::entity::effect::MobEffect;
use crate::entity::living::LivingEntity;
pub struct WitherMobEffect;
@@ -18,20 +18,14 @@ impl MobEffect for WitherMobEffect {
}
}
fn apply_effect_tick<'a>(
&'a self,
living: &'a LivingEntity,
_amplifier: u8,
) -> EffectFuture<'a, ()> {
Box::pin(async move {
let dyn_self = living
.entity
.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);
}
})
fn apply_effect_tick(&self, living: &LivingEntity, _amplifier: u8) {
let dyn_self = living
.entity
.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 {
player.living_entity.pickup(&self.entity, 1);
self.entity.remove();
let player_clone = player.clone();
let amount = self.amount as i32;
tokio::spawn(async move {
let remaining = player_clone.apply_mending_from_xp(amount).await;
if remaining > 0 {
player_clone.add_experience_points(remaining).await;
}
});
let remaining = player.apply_mending_from_xp(amount);
if remaining > 0 {
player.add_experience_points(remaining);
}
}
}
}

View File

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

View File

@@ -13,9 +13,7 @@ use pumpkin_util::math::{
};
use crate::{
entity::{
Entity, EntityBase, EntityBaseFuture, NbtFuture, living::LivingEntity, player::Player,
},
entity::{Entity, EntityBase, living::LivingEntity, player::Player},
server::Server,
};
@@ -161,85 +159,81 @@ impl InteractionEntity {
}
impl EntityBase for InteractionEntity {
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
nbt.put_float(
"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);
fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_float(
"width",
*self
.width
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = width;
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
nbt.put_float(
"height",
*self
.height
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = height;
self.response.store(response, Ordering::Relaxed);
self.update_dimensions();
.unwrap_or_else(std::sync::PoisonError::into_inner),
);
nbt.put_bool("response", self.response.load(Ordering::Relaxed));
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;
}
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()));
}
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;
}
})
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(&self, nbt: &NbtCompound) {
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) {}
@@ -331,21 +325,15 @@ impl EntityBase for InteractionEntity {
false
}
fn interact<'a>(
&'a self,
player: &'a Arc<Player>,
_item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move {
let timestamp = self.entity.world.load().get_world_age();
*self
.interaction
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(PlayerAction {
player: player.gameprofile.id,
timestamp,
});
true
})
fn interact(&self, player: &Arc<Player>, _item_stack: &mut ItemStack) -> bool {
let timestamp = self.entity.world.load().get_world_age();
*self
.interaction
.lock()
.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 {
entity: Entity,
@@ -381,23 +381,18 @@ impl ItemEntity {
if age >= 6000 {
let entity_id = entity.entity_id;
let world = entity.world.load_full();
tokio::spawn(async move {
let mut despawn_event =
crate::plugin::api::events::entity::item_despawn::ItemDespawnEvent::new(
entity_id,
);
if let Some(server) = world.server.upgrade() {
server
.plugin_manager
.fire(&server, &mut despawn_event)
.await;
}
if !despawn_event.cancelled
&& let Some(e) = world.get_entity_by_id(entity_id)
{
e.get_entity().remove();
}
});
let mut despawn_event =
crate::plugin::api::events::entity::item_despawn::ItemDespawnEvent::new(entity_id);
if let Some(server) = world.server.upgrade() {
server
.plugin_manager
.fire_blocking(&server, &mut despawn_event);
}
if !despawn_event.cancelled
&& let Some(e) = world.get_entity_by_id(entity_id)
{
e.get_entity().remove();
}
return false;
}
@@ -608,51 +603,47 @@ impl EntityBase for ItemEntity {
0.04
}
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
let item = self
.item_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut item_compound = NbtCompound::new();
item.write_item_stack(&mut item_compound);
nbt.put_compound("Item", item_compound);
fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
let item = self
.item_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut item_compound = NbtCompound::new();
item.write_item_stack(&mut item_compound);
nbt.put_compound("Item", item_compound);
nbt.put_short("Age", self.item_age.load(Ordering::Relaxed) as i16);
nbt.put_short(
"PickupDelay",
self.pickup_delay.load(Ordering::Relaxed) as i16,
);
nbt.put_short("Health", self.health.load(Relaxed) as i16);
})
nbt.put_short("Age", self.item_age.load(Ordering::Relaxed) as i16);
nbt.put_short(
"PickupDelay",
self.pickup_delay.load(Ordering::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, ()> {
Box::pin(async {
// Restore the item stack from the "Item" compound
if let Some(item_compound) = nbt.get_compound("Item")
&& let Some(stack) = ItemStack::read_item_stack(item_compound)
{
*self
.item_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
}
fn read_custom_nbt(&self, nbt: &NbtCompound) {
// Restore the item stack from the "Item" compound
if let Some(item_compound) = nbt.get_compound("Item")
&& let Some(stack) = ItemStack::read_item_stack(item_compound)
{
*self
.item_stack
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = stack;
}
// Vanilla stores Age as a short
self.item_age
.store(nbt.get_short("Age").unwrap_or(0) as u32, Ordering::Relaxed);
// Vanilla stores Age as a short
self.item_age
.store(nbt.get_short("Age").unwrap_or(0) as u32, Ordering::Relaxed);
// Vanilla stores PickupDelay as a short
if let Some(delay) = nbt.get_short("PickupDelay") {
self.pickup_delay.store(delay as u8, Ordering::Relaxed);
}
// Vanilla stores PickupDelay as a short
if let Some(delay) = nbt.get_short("PickupDelay") {
self.pickup_delay.store(delay as u8, Ordering::Relaxed);
}
// Vanilla stores Health as a short
if let Some(health) = nbt.get_short("Health") {
self.health.store(health as f32, Relaxed);
}
})
// Vanilla stores Health as a short
if let Some(health) = nbt.get_short("Health") {
self.health.store(health as f32, Relaxed);
}
}
fn cast_any(&self) -> &dyn std::any::Any {

View File

@@ -192,7 +192,7 @@ impl 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 life = self.life.load(Ordering::Relaxed);
@@ -261,14 +261,7 @@ impl EntityBase for LightningBoltEntity {
}
let hit_id = hit_entity.get_entity().entity_id;
if hit_guard.insert(hit_id) {
let caller_clone = caller.clone();
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;
}
});
hit_entity.on_lightning_strike(hit_entity.as_ref(), self);
}
}
}

View File

@@ -24,6 +24,7 @@ use tracing::warn;
use super::experience_orb::ExperienceOrbEntity;
use super::{Entity, EntityBase, NBTStorageInit};
use crate::block::OnLandedUponArgs;
use crate::entity::NBTStorage;
use crate::entity::attributes::AttributeInstance;
use crate::entity::attributes::Modifier;
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::slime::SlimeEntity;
use crate::entity::player::statistics::{CustomStatistic, StatisticCategory};
use crate::entity::{NBTStorage, NbtFuture};
use crate::server::Server;
use crate::world::loot::{LootContextParameters, LootTableExt};
use crossbeam::atomic::AtomicCell;
@@ -1443,9 +1443,13 @@ impl LivingEntity {
fall_distance: f32,
damage_per_distance: f32,
) {
let may_fly = caller
.get_player()
.is_some_and(|player| player.abilities.blocking_lock().allow_flying);
let may_fly = caller.get_player().is_some_and(|player| {
player
.abilities
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.allow_flying
});
if may_fly || self.is_immune_to_fall_damage() {
return;
}
@@ -1472,7 +1476,7 @@ impl LivingEntity {
}
}
pub async fn get_death_message(
pub fn get_death_message(
dyn_self: &dyn EntityBase,
damage_type: DamageType,
source: Option<&dyn EntityBase>,
@@ -1486,16 +1490,13 @@ impl LivingEntity {
TextComponent::translate_cross(
format!("death.attack.{}.player", damage_type.message_id),
format!("death.attack.{}.player", damage_type.message_id),
[
dyn_self.get_display_name().await,
cause.get_display_name().await,
],
[dyn_self.get_display_name(), cause.get_display_name()],
)
} else {
TextComponent::translate_cross(
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(
translation::java::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("[")
.add_child(TextComponent::translate_cross(
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("]")),
}
@@ -1618,7 +1619,7 @@ impl LivingEntity {
};
for (effect_type, amplifier) in active_effects_vec {
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 };
if self.entity.entity_type == &EntityType::PLAYER && show_death_messages {
//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() {
for player in server.get_all_players() {
player.send_system_message(&death_message).await;
@@ -1816,15 +1817,7 @@ impl LivingEntity {
}
for (mob_effect, amplifier) in effects_to_apply {
let entity_id = self.entity.entity_id;
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;
}
});
mob_effect.apply_effect_tick(self, amplifier);
}
}
@@ -2093,103 +2086,97 @@ impl LivingEntity {
}
impl LivingEntity {
pub fn write_living_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
nbt.put("Health", NbtTag::Float(self.health.load()));
// Avoid persisting a lethal fall distance when the entity is dead to prevent death loops
let fall_distance = if self.dead.load(Relaxed) {
0.0
} else {
self.fall_distance.load()
pub fn write_living_nbt(&self, nbt: &mut NbtCompound) {
nbt.put("Health", NbtTag::Float(self.health.load()));
// Avoid persisting a lethal fall distance when the entity is dead to prevent death loops
let fall_distance = if self.dead.load(Relaxed) {
0.0
} else {
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
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
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);
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(&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
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
effects.values().cloned().collect()
};
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);
}
for effect in read_effects {
active_effects.insert(effect.effect_type, effect);
}
}
}
})
}
// todo more...
}
@@ -2828,9 +2815,7 @@ impl EntityBase for LivingEntity {
.cooldown_group
.clone()
.unwrap_or_else(|| item.item.registry_key.to_string());
player
.start_cooldown(group, (cooldown.seconds * 20.0) as i32)
.await;
player.start_cooldown(group, (cooldown.seconds * 20.0) as i32);
}
}
@@ -2932,7 +2917,12 @@ impl LivingEntity {
}
ConsumeEffect::TeleportRandomly(diameter) => {
// 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 {
vehicle
.get_entity()
@@ -2959,7 +2949,7 @@ impl LivingEntity {
self.fall_distance.store(0.0);
// Vanilla broadcasts entity event 46 (teleport particles) on success.
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(
Sound::ItemChorusFruitTeleport,
SoundCategory::Players,

View File

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

View File

@@ -14,7 +14,7 @@ use pumpkin_world::chunk::ChunkHeightmapType;
use rand::RngExt;
use crate::entity::mob::{Mob, MobEntity};
use crate::entity::{Entity, EntityBase, NbtFuture};
use crate::entity::{Entity, EntityBase};
use crate::world::World;
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, ()> {
Box::pin(async move {
let flags: u8 = if self.is_roosting() { ROOSTING_FLAG } else { 0 };
nbt.put_byte("BatFlags", i8::try_from(flags).unwrap_or(0));
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
let flags: u8 = if self.is_roosting() { ROOSTING_FLAG } else { 0 };
nbt.put_byte("BatFlags", i8::try_from(flags).unwrap_or(0));
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
let flags = u8::try_from(nbt.get_byte("BatFlags").unwrap_or(0)).unwrap_or(0);
let roosting = (flags & ROOSTING_FLAG) != 0;
self.set_roosting(roosting);
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
let flags = u8::try_from(nbt.get_byte("BatFlags").unwrap_or(0)).unwrap_or(0);
let roosting = (flags & ROOSTING_FLAG) != 0;
self.set_roosting(roosting);
}
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::entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
@@ -397,28 +397,24 @@ impl CreakingEntity {
}
impl Mob for CreakingEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
if let Some(pos) = self.get_home_pos() {
let mut sub = NbtCompound::new();
sub.put_int("x", pos.0.x);
sub.put_int("y", pos.0.y);
sub.put_int("z", pos.0.z);
nbt.put_compound("home_pos", sub);
}
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
if let Some(pos) = self.get_home_pos() {
let mut sub = NbtCompound::new();
sub.put_int("x", pos.0.x);
sub.put_int("y", pos.0.y);
sub.put_int("z", pos.0.z);
nbt.put_compound("home_pos", sub);
}
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
if let Some(sub) = nbt.get_compound("home_pos")
&& let (Some(x), Some(y), Some(z)) =
(sub.get_int("x"), sub.get_int("y"), sub.get_int("z"))
{
let pos = BlockPos::new(x, y, z);
self.set_transient(pos);
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
if let Some(sub) = nbt.get_compound("home_pos")
&& let (Some(x), Some(y), Some(z)) =
(sub.get_int("x"), sub.get_int("y"), sub.get_int("z"))
{
let pos = BlockPos::new(x, y, z);
self.set_transient(pos);
}
}
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 crate::entity::{
Entity, EntityBase, EntityBaseFuture, NbtFuture,
Entity, EntityBase,
ai::goal::{
active_target::ActiveTargetGoal, creeper_ignite::CreeperIgniteGoal,
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 radius = self.explosion_radius.load(Ordering::Relaxed) as f32;
let multiplier = if self.charged.load(Ordering::Relaxed) {
@@ -115,75 +115,66 @@ impl CreeperEntity {
.store(true, Ordering::Relaxed);
let world = entity.world.load();
let pos = entity.pos.load();
world
.explode(
pos,
radius * multiplier,
crate::world::ExplosionInteraction::Mob,
)
.await;
world.explode(
pos,
radius * multiplier,
crate::world::ExplosionInteraction::Mob,
);
// TODO: spawn area effect cloud with potion effects
entity.remove();
}
}
impl Mob for CreeperEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
nbt.put_bool("powered", self.charged.load(Ordering::Relaxed));
nbt.put_short("Fuse", self.fuse_time.load(Ordering::Relaxed) as i16);
nbt.put_byte(
"ExplosionRadius",
self.explosion_radius.load(Ordering::Relaxed) as i8,
);
nbt.put_bool("ignited", self.ignited.load(Ordering::Relaxed));
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_bool("powered", self.charged.load(Ordering::Relaxed));
nbt.put_short("Fuse", self.fuse_time.load(Ordering::Relaxed) as i16);
nbt.put_byte(
"ExplosionRadius",
self.explosion_radius.load(Ordering::Relaxed) as i8,
);
nbt.put_bool("ignited", self.ignited.load(Ordering::Relaxed));
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
if let Some(powered) = nbt.get_bool("powered") {
self.charged.store(powered, Ordering::Relaxed);
}
if let Some(fuse) = nbt.get_short("Fuse") {
self.fuse_time.store(i32::from(fuse), Ordering::Relaxed);
}
if let Some(radius) = nbt.get_byte("ExplosionRadius") {
self.explosion_radius
.store(i32::from(radius), Ordering::Relaxed);
}
if let Some(ignited) = nbt.get_bool("ignited") {
self.ignited.store(ignited, Ordering::Relaxed);
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
if let Some(powered) = nbt.get_bool("powered") {
self.charged.store(powered, Ordering::Relaxed);
}
if let Some(fuse) = nbt.get_short("Fuse") {
self.fuse_time.store(i32::from(fuse), Ordering::Relaxed);
}
if let Some(radius) = nbt.get_byte("ExplosionRadius") {
self.explosion_radius
.store(i32::from(radius), Ordering::Relaxed);
}
if let Some(ignited) = nbt.get_bool("ignited") {
self.ignited.store(ignited, Ordering::Relaxed);
}
}
fn get_mob_entity(&self) -> &MobEntity {
&self.mob_entity
}
fn mob_on_lightning_strike<'a>(
&'a self,
caller: &'a dyn EntityBase,
lightning: &'a crate::entity::lightning::LightningBoltEntity,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
self.charged.store(true, Ordering::Relaxed);
self.mob_entity.living_entity.entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::CHARGED,
true,
)],
None,
);
self.mob_entity
.living_entity
.on_lightning_strike(caller, lightning)
.await;
})
fn mob_on_lightning_strike(
&self,
caller: &dyn EntityBase,
lightning: &crate::entity::lightning::LightningBoltEntity,
) {
self.charged.store(true, Ordering::Relaxed);
self.mob_entity.living_entity.entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::CHARGED,
true,
)],
None,
);
self.mob_entity
.living_entity
.on_lightning_strike(caller, lightning);
}
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;
if !entity.is_alive() {
return;
@@ -218,52 +209,41 @@ impl Mob for CreeperEntity {
if new_fuse >= fuse_time {
self.current_fuse_time.store(fuse_time, Ordering::Relaxed);
let caller_clone = caller.clone();
tokio::spawn(async move {
if let Some(creeper) = caller_clone.cast_any().downcast_ref::<Self>() {
creeper.explode().await;
}
});
self.explode();
}
}
fn mob_interact<'a>(
&'a self,
player: &'a Arc<Player>,
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);
}
fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
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 world = entity.world.load();
let pos = entity.pos.load();
let entity = &self.mob_entity.living_entity.entity;
let world = entity.world.load();
let pos = entity.pos.load();
world.play_sound_fine(
Sound::ItemFlintandsteelUse,
SoundCategory::Hostile,
&pos,
1.0,
rand::random::<f32>() * 0.4 + 0.8,
);
world.play_sound_fine(
Sound::ItemFlintandsteelUse,
SoundCategory::Hostile,
&pos,
1.0,
rand::random::<f32>() * 0.4 + 0.8,
);
self.ignited.store(true, Ordering::Relaxed);
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::IS_IGNITED,
true,
)],
None,
);
self.ignited.store(true, Ordering::Relaxed);
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::creeper::IS_IGNITED,
true,
)],
None,
);
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
// TODO: Handle DamageResult::Broken to broadcast item break and update player slot.
let _ = item_stack.damage_item(1);
}
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
// TODO: Handle DamageResult::Broken to broadcast item break and update player slot.
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 crate::entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::{
goal::{
active_target::ActiveTargetGoal, chase_player::ChasePlayerGoal,
@@ -412,20 +412,16 @@ impl EndermanEntity {
}
impl Mob for EndermanEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
if let Some(block_state) = self.carried_block.load() {
nbt.put_int("carriedBlockState", block_state.as_u16() as i32);
}
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
if let Some(block_state) = self.carried_block.load() {
nbt.put_int("carriedBlockState", block_state.as_u16() as i32);
}
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {
if let Some(block_state) = nbt.get_int("carriedBlockState") {
self.set_carried_block(BlockStateId::new(block_state as u16));
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
if let Some(block_state) = nbt.get_int("carriedBlockState") {
self.set_carried_block(BlockStateId::new(block_state as u16));
}
}
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
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;
if !entity.is_alive() {
return;
@@ -447,13 +443,7 @@ impl Mob for EndermanEntity {
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());
if entity.touching_water.load(Ordering::SeqCst) || raining_at_feet || raining_at_head {
let entity_id = entity.entity_id;
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);
}
});
caller.damage(caller.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 crate::entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::goal::{
Controls, Goal, active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, swim::SwimGoal, wander_around::WanderAroundGoal,
@@ -161,20 +161,16 @@ impl Mob for EvokerEntity {
Some(self)
}
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.write_raider_nbt(nbt);
nbt.put_int("SpellTicks", self.get_spell_casting_time());
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
self.write_raider_nbt(nbt);
nbt.put_int("SpellTicks", self.get_spell_casting_time());
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.read_raider_nbt(nbt);
if let Some(ticks) = nbt.get_int("SpellTicks") {
self.set_spell_casting_time(ticks);
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
self.read_raider_nbt(nbt);
if let Some(ticks) = nbt.get_int("SpellTicks") {
self.set_spell_casting_time(ticks);
}
}
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::projectile::fireball::FireballEntity;
use crate::entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::goal::{Controls, Goal},
mob::{Mob, MobEntity},
};
@@ -151,21 +151,15 @@ impl Mob for GhastEntity {
}
}
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
let power = self.get_explosion_power();
nbt.put_byte("ExplosionPower", i8::try_from(power).unwrap_or(1));
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
let power = self.get_explosion_power();
nbt.put_byte("ExplosionPower", i8::try_from(power).unwrap_or(1));
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
if let Some(power) = nbt.get_byte("ExplosionPower") {
self.set_explosion_power(
u8::try_from(power).unwrap_or(Self::DEFAULT_EXPLOSION_POWER),
);
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
if let Some(power) = nbt.get_byte("ExplosionPower") {
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 {

View File

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

View File

@@ -1,5 +1,4 @@
use super::{Entity, EntityBase, NbtFuture, ai::pathfinder::Navigator, living::LivingEntity};
use crate::entity::EntityBaseFuture;
use super::{Entity, EntityBase, ai::pathfinder::Navigator, living::LivingEntity};
use crate::entity::ai::control::MoveControlTrait;
use crate::entity::ai::control::look_control::LookControl;
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.
fn mob_java_spawn_metadata(
&self,
_version: JavaMinecraftVersion,
) -> EntityBaseFuture<'_, Option<Box<[u8]>>> {
Box::pin(async { None })
fn mob_java_spawn_metadata(&self, _version: JavaMinecraftVersion) -> Option<Box<[u8]>> {
None
}
/// Metadata which must accompany this mob whenever it is spawned for a Bedrock client.
fn mob_bedrock_spawn_metadata(
&self,
) -> EntityBaseFuture<
'_,
Option<pumpkin_protocol::bedrock::client::set_actor_data::SyncedActorDataList>,
> {
Box::pin(async { None })
) -> Option<pumpkin_protocol::bedrock::client::set_actor_data::SyncedActorDataList> {
None
}
fn get_job_site(&self) -> Option<BlockPos> {
@@ -699,115 +692,105 @@ pub trait Mob: EntityBase + Send + Sync {
None
}
fn populate_default_equipment_slots<'a>(
&'a self,
_world: &'a Arc<World>,
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
if rand::random::<f32>()
< MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier
{
let mut armor_type = rand::random_range(0..3);
for _ in 1..=3 {
if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE {
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_slots(
&self,
_world: &Arc<World>,
difficulty: &crate::entity::mob::equipment::RegionalDifficulty,
) {
if rand::random::<f32>()
< MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier
{
let mut armor_type = rand::random_range(0..3);
for _ in 1..=3 {
if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE {
armor_type += 1;
}
}
})
}
fn populate_default_equipment_enchantments<'a>(
&'a self,
difficulty: &'a crate::entity::mob::equipment::RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
self.enchant_spawned_weapon(difficulty).await;
for slot in &MobEntity::EQUIPMENT_POPULATION_ORDER {
self.enchant_spawned_armor(slot, difficulty).await;
}
})
}
let partial_chance = if difficulty.base_difficulty == Difficulty::Hard {
0.1f32
} else {
0.25f32
};
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 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,
);
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 mob_write_nbt<'a>(&'a self, _nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async {})
fn populate_default_equipment_enchantments(
&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, ()> {
Box::pin(async {})
fn enchant_spawned_weapon(
&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.
fn set_mob_target(&self, target: Option<Arc<dyn EntityBase>>) {
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;
let world = mob.living_entity.entity.world.load_full();
let entity_id = mob.living_entity.entity.entity_id;
tokio::spawn(async move {
if let Some(server) = world.server.upgrade() {
let mut event =
crate::plugin::api::events::entity::entity_target::EntityTargetEvent::new(
entity_id, target_id,
);
if let Some(server) = world.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
});
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn mob_interact<'a>(
&'a self,
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 mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
self.get_mob_entity().mob_interact(player, item_stack)
}
fn tame<'a>(&'a self, player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_tame::EntityTameEvent::new(
mob.living_entity.entity.entity_id,
player.clone(),
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
fn tame(&self, player: &Arc<Player>) {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_tame::EntityTameEvent::new(
mob.living_entity.entity.entity_id,
player.clone(),
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn breed(&self, father_id: i32, mother_id: i32, child_id: i32) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_breed::EntityBreedEvent::new(
father_id, mother_id, child_id,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
fn breed(&self, father_id: i32, mother_id: i32, child_id: i32) {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_breed::EntityBreedEvent::new(
father_id, mother_id, child_id,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire_blocking(&server, &mut event);
}
}
fn dye<'a>(
&'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(
fn dye(
&self,
human_entity_id: Option<i32>,
ticks_in_love: i32,
) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event = crate::plugin::api::events::entity::entity_enter_love_mode::EntityEnterLoveModeEvent::new(
color: crate::plugin::api::events::entity::entity_dye::DyeColor,
player: Option<&Arc<Player>>,
) {
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_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,
human_entity_id,
ticks_in_love,
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;
}
})
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) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let mob = self.get_mob_entity();
let mut event =
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(
fn break_door(&self, block_pos: BlockPos) {
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,
block_name,
);
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
}
})
if let Some(server) = mob.living_entity.entity.world.load().server.upgrade() {
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>) {}
@@ -1006,17 +961,14 @@ pub trait Mob: EntityBase + Send + Sync {
None
}
fn mob_on_lightning_strike<'a>(
&'a self,
caller: &'a dyn EntityBase,
lightning: &'a crate::entity::lightning::LightningBoltEntity,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
self.get_mob_entity()
.living_entity
.on_lightning_strike(caller, lightning)
.await;
})
fn mob_on_lightning_strike(
&self,
caller: &dyn EntityBase,
lightning: &crate::entity::lightning::LightningBoltEntity,
) {
self.get_mob_entity()
.living_entity
.on_lightning_strike(caller, lightning);
}
}
impl<T: Mob + Send + 'static> EntityBase for T {
@@ -1024,14 +976,12 @@ impl<T: Mob + Send + 'static> EntityBase for T {
Some(self)
}
fn on_lightning_strike<'a>(
&'a self,
caller: &'a dyn EntityBase,
lightning: &'a crate::entity::lightning::LightningBoltEntity,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
self.mob_on_lightning_strike(caller, lightning).await;
})
fn on_lightning_strike(
&self,
caller: &dyn EntityBase,
lightning: &crate::entity::lightning::LightningBoltEntity,
) {
self.mob_on_lightning_strike(caller, lightning);
}
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
}
fn interact<'a>(
&'a self,
player: &'a Arc<Player>,
item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { self.mob_interact(player, item_stack).await })
fn interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
self.mob_interact(player, item_stack)
}
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)
}
fn write_custom_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.get_mob_entity().write_mob_nbt(nbt);
if let Some(ageable) = self.as_ageable() {
ageable.write_ageable_nbt(nbt);
}
if let Some(animal) = self.as_animal() {
animal.write_animal_nbt(nbt);
}
if let Some(tamable) = self.as_tamable() {
tamable.write_tamable_nbt(nbt);
}
self.mob_write_nbt(nbt).await;
})
fn write_custom_nbt(&self, nbt: &mut NbtCompound) {
self.get_mob_entity().write_mob_nbt(nbt);
if let Some(ageable) = self.as_ageable() {
ageable.write_ageable_nbt(nbt);
}
if let Some(animal) = self.as_animal() {
animal.write_animal_nbt(nbt);
}
if let Some(tamable) = self.as_tamable() {
tamable.write_tamable_nbt(nbt);
}
self.mob_write_nbt(nbt);
}
fn read_custom_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.get_mob_entity().read_mob_nbt(nbt);
if let Some(ageable) = self.as_ageable() {
ageable.read_ageable_nbt(nbt);
}
if let Some(animal) = self.as_animal() {
animal.read_animal_nbt(nbt);
}
if let Some(tamable) = self.as_tamable() {
tamable.read_tamable_nbt(nbt);
}
self.mob_read_nbt(nbt).await;
})
fn read_custom_nbt(&self, nbt: &NbtCompound) {
self.get_mob_entity().read_mob_nbt(nbt);
if let Some(ageable) = self.as_ageable() {
ageable.read_ageable_nbt(nbt);
}
if let Some(animal) = self.as_animal() {
animal.read_animal_nbt(nbt);
}
if let Some(tamable) = self.as_tamable() {
tamable.read_tamable_nbt(nbt);
}
self.mob_read_nbt(nbt);
}
fn get_gravity(&self) -> f64 {

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
use std::sync::{
Arc, Weak,
Arc, Mutex, Weak,
atomic::{AtomicBool, Ordering},
};
@@ -10,10 +10,9 @@ use pumpkin_data::tracked_data;
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_nbt::tag::NbtTag;
use pumpkin_protocol::java::client::play::Metadata;
use tokio::sync::Mutex;
use crate::entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
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> {
let mut inv = self.inventory.lock().await;
pub fn add_to_inventory(&self, item: ItemStack) -> Option<ItemStack> {
let mut inv = self
.inventory
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if inv.len() < Self::INVENTORY_SIZE {
inv.push(item);
None
@@ -163,56 +165,49 @@ impl Mob for PillagerEntity {
}
fn mob_init_data_tracker(&self) {
let entity = self.get_entity();
if self.is_charging_crossbow() {
entity.send_meta_data(
&[Metadata::new(
tracked_data::pillager::IS_CHARGING_CROSSBOW,
true,
)],
None,
);
self.set_charging_crossbow(self.is_charging_crossbow.load(Ordering::Relaxed));
}
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
self.write_raider_nbt(nbt);
nbt.put_bool("CanPickUpLoot", true);
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, ()> {
Box::pin(async move {
self.write_raider_nbt(nbt);
nbt.put_bool("CanPickUpLoot", true);
fn mob_read_nbt(&self, nbt: &NbtCompound) {
self.read_raider_nbt(nbt);
let inv = self.inventory.lock().await;
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);
if let Some(inv_list) = nbt.get_list("Inventory") {
let mut inv = self
.inventory
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
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 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(

View File

@@ -5,7 +5,7 @@ use pumpkin_data::sound::Sound;
use pumpkin_nbt::compound::NbtCompound;
use crate::entity::{
Entity, NbtFuture,
Entity,
ai::goal::{
active_target::ActiveTargetGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal, swim::SwimGoal,
@@ -97,16 +97,12 @@ impl Mob for RavagerEntity {
Some(self)
}
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.write_raider_nbt(nbt);
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
self.write_raider_nbt(nbt);
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.read_raider_nbt(nbt);
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
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::mob::{Mob, MobEntity};
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 NO_COLOR: u8 = 16;
@@ -322,26 +322,22 @@ impl ShulkerEntity {
}
impl Mob for ShulkerEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
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("Color", self.color.load(Ordering::Relaxed) as i8);
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
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("Color", self.color.load(Ordering::Relaxed) as i8);
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
if let Some(face) = nbt.get_byte("AttachFace") {
self.attach_face.store(face as u8, Ordering::Relaxed);
}
if let Some(peek) = nbt.get_byte("PeekAmount") {
self.peek_amount.store(peek as u8, Ordering::Relaxed);
}
if let Some(color) = nbt.get_byte("Color") {
self.color.store(color as u8, Ordering::Relaxed);
}
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
if let Some(face) = nbt.get_byte("AttachFace") {
self.attach_face.store(face as u8, Ordering::Relaxed);
}
if let Some(peek) = nbt.get_byte("PeekAmount") {
self.peek_amount.store(peek as u8, Ordering::Relaxed);
}
if let Some(color) = nbt.get_byte("Color") {
self.color.store(color as u8, Ordering::Relaxed);
}
}
fn get_mob_entity(&self) -> &MobEntity {

View File

@@ -7,7 +7,7 @@ use pumpkin_data::item_stack::ItemStack;
use pumpkin_util::Difficulty;
use crate::entity::{
Entity, EntityBaseFuture,
Entity,
ai::goal::{
active_target::ActiveTargetGoal, bow_attack::BowAttackGoal,
look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal,
@@ -76,57 +76,55 @@ impl Mob for SkeletonEntityBase {
&self.mob_entity
}
fn populate_default_equipment_slots<'a>(
&'a self,
_world: &'a Arc<World>,
difficulty: &'a RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
// Default armor slots (super.populateDefaultEquipmentSlots)
if rand::random::<f32>()
< MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier
{
let mut armor_type = rand::random_range(0..3);
for _ in 1..=3 {
if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE {
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));
}
fn populate_default_equipment_slots(
&self,
_world: &Arc<World>,
difficulty: &RegionalDifficulty,
) {
// Default armor slots (super.populateDefaultEquipmentSlots)
if rand::random::<f32>()
< MobEntity::MAX_WEARING_ARMOR_CHANCE * difficulty.special_multiplier
{
let mut armor_type = rand::random_range(0..3);
for _ in 1..=3 {
if rand::random::<f32>() < MobEntity::WEARING_ARMOR_UPGRADE_MATERIAL_CHANCE {
armor_type += 1;
}
}
// 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 mut equipment = living
.entity_equipment
.lock()
.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 crate::entity::{
Entity, EntityBaseFuture,
Entity,
mob::{Mob, MobEntity, equipment::RegionalDifficulty, skeleton::SkeletonEntityBase},
};
use crate::world::World;
@@ -23,20 +23,17 @@ impl Mob for SkeletonEntity {
&self.entity.mob_entity
}
fn populate_default_equipment_slots<'a>(
&'a self,
world: &'a Arc<World>,
difficulty: &'a RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
fn populate_default_equipment_slots(
&self,
world: &Arc<World>,
difficulty: &RegionalDifficulty,
) {
self.entity
.populate_default_equipment_slots(world, difficulty)
.populate_default_equipment_slots(world, difficulty);
}
fn populate_default_equipment_enchantments<'a>(
&'a self,
difficulty: &'a RegionalDifficulty,
) -> EntityBaseFuture<'a, ()> {
fn populate_default_equipment_enchantments(&self, difficulty: &RegionalDifficulty) {
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 crate::entity::{
Entity, EntityBase, NbtFuture,
Entity, EntityBase,
ai::control::{Control, MoveControlTrait},
ai::goal::{Goal, active_target::ActiveTargetGoal},
mob::{Mob, MobEntity},
@@ -265,21 +265,17 @@ impl SlimeEntity {
}
impl Mob for SlimeEntity {
fn mob_write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
nbt.put_int("Size", self.get_size() - 1);
nbt.put_bool("wasOnGround", self.was_on_ground.load(Ordering::Relaxed));
})
fn mob_write_nbt(&self, nbt: &mut NbtCompound) {
nbt.put_int("Size", self.get_size() - 1);
nbt.put_bool("wasOnGround", self.was_on_ground.load(Ordering::Relaxed));
}
fn mob_read_nbt<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> {
Box::pin(async move {
self.set_size(nbt.get_int("Size").unwrap_or(0) + 1, false);
self.was_on_ground.store(
nbt.get_bool("wasOnGround").unwrap_or(false),
Ordering::Relaxed,
);
})
fn mob_read_nbt(&self, nbt: &NbtCompound) {
self.set_size(nbt.get_int("Size").unwrap_or(0) + 1, false);
self.was_on_ground.store(
nbt.get_bool("wasOnGround").unwrap_or(false),
Ordering::Relaxed,
);
}
fn get_mob_entity(&self) -> &MobEntity {

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