mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
refactor(entities): from async to sync Part 2
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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("]"))
|
||||
|
||||
@@ -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()],
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()),
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ¤t {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ¢er);
|
||||
}
|
||||
}
|
||||
})
|
||||
world.play_sound(Sound::EntitySilverfishHurt, SoundCategory::Hostile, ¢er);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
¢er,
|
||||
);
|
||||
})
|
||||
world.play_sound(
|
||||
Sound::EntityBreezeWindBurst,
|
||||
SoundCategory::Hostile,
|
||||
¢er,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user