715 lines
22 KiB
Rust
715 lines
22 KiB
Rust
use crate::entity::player::statistics::StatisticCategory;
|
|
use crate::{entity::EntityBaseFuture, server::Server};
|
|
use core::f32;
|
|
use pumpkin_data::damage::DamageType;
|
|
use pumpkin_data::data_component_impl::DamageResistantImpl;
|
|
use pumpkin_data::data_component_impl::DamageResistantType;
|
|
use pumpkin_data::item_stack::ItemStack;
|
|
use pumpkin_data::packet::CURRENT_MC_VERSION;
|
|
use pumpkin_nbt::compound::NbtCompound;
|
|
use pumpkin_protocol::bedrock::client::CAddItemActor;
|
|
use pumpkin_protocol::bedrock::network_item::ItemStackWrapper;
|
|
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
|
use pumpkin_protocol::codec::var_long::VarLong;
|
|
use pumpkin_protocol::codec::var_ulong::VarULong;
|
|
use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata};
|
|
use pumpkin_util::math::atomic_f32::AtomicF32;
|
|
use pumpkin_util::math::vector3::Vector3;
|
|
use std::sync::atomic::Ordering::{AcqRel, Relaxed};
|
|
|
|
use std::sync::{
|
|
Arc, Mutex,
|
|
atomic::{
|
|
AtomicBool, AtomicU8, AtomicU32,
|
|
Ordering::{self},
|
|
},
|
|
};
|
|
|
|
use super::{Entity, EntityBase, living::LivingEntity, player::Player};
|
|
|
|
pub struct ItemEntity {
|
|
entity: Entity,
|
|
item_age: AtomicU32,
|
|
// These cannot be atomic values because we mutate their state based on what they are; we run
|
|
// into the ABA problem
|
|
item_stack: Mutex<ItemStack>,
|
|
pickup_delay: AtomicU8,
|
|
health: AtomicF32,
|
|
never_despawn: AtomicBool,
|
|
never_pickup: AtomicBool,
|
|
}
|
|
|
|
const ITEM_UPDATE_INTERVAL: u32 = 20;
|
|
|
|
impl ItemEntity {
|
|
pub fn new(entity: Entity, item_stack: ItemStack) -> Self {
|
|
entity.velocity.store(Vector3::new(
|
|
rand::random::<f64>().mul_add(0.2, -0.1),
|
|
0.2,
|
|
rand::random::<f64>().mul_add(0.2, -0.1),
|
|
));
|
|
entity.yaw.store(rand::random::<f32>() * 360.0);
|
|
|
|
// Set fire immunity for certain items
|
|
if let Some(res) = item_stack.get_data_component::<DamageResistantImpl>()
|
|
&& res.res_type == DamageResistantType::Fire
|
|
{
|
|
entity.fire_immune.store(true, Ordering::Relaxed);
|
|
}
|
|
|
|
Self {
|
|
entity,
|
|
item_stack: Mutex::new(item_stack),
|
|
item_age: AtomicU32::new(0),
|
|
pickup_delay: AtomicU8::new(10), // Vanilla pickup delay is 10 ticks
|
|
health: AtomicF32::new(5.0),
|
|
never_despawn: AtomicBool::new(false),
|
|
never_pickup: AtomicBool::new(false),
|
|
}
|
|
}
|
|
|
|
pub fn new_with_velocity(
|
|
entity: Entity,
|
|
item_stack: ItemStack,
|
|
velocity: Vector3<f64>,
|
|
pickup_delay: u8,
|
|
) -> Self {
|
|
entity.velocity.store(velocity);
|
|
entity.yaw.store(rand::random::<f32>() * 360.0);
|
|
|
|
// Set fire immunity for certain items
|
|
if let Some(res) = item_stack.get_data_component::<DamageResistantImpl>()
|
|
&& res.res_type == DamageResistantType::Fire
|
|
{
|
|
entity.fire_immune.store(true, Ordering::Relaxed);
|
|
}
|
|
|
|
Self {
|
|
entity,
|
|
item_stack: Mutex::new(item_stack),
|
|
item_age: AtomicU32::new(0),
|
|
pickup_delay: AtomicU8::new(pickup_delay), // Vanilla pickup delay is 10 ticks
|
|
health: AtomicF32::new(5.0),
|
|
never_despawn: AtomicBool::new(false),
|
|
never_pickup: AtomicBool::new(false),
|
|
}
|
|
}
|
|
|
|
/// Creates an `ItemEntity` for restoring from NBT without random velocity.
|
|
/// The velocity and position will be set by `Entity::read_nbt_non_mut`.
|
|
pub fn new_empty(entity: Entity) -> Self {
|
|
Self {
|
|
entity,
|
|
item_stack: Mutex::new(ItemStack::new(1, &pumpkin_data::item::Item::AIR)),
|
|
item_age: AtomicU32::new(0),
|
|
pickup_delay: AtomicU8::new(0),
|
|
health: AtomicF32::new(5.0),
|
|
never_despawn: AtomicBool::new(false),
|
|
never_pickup: AtomicBool::new(false),
|
|
}
|
|
}
|
|
|
|
pub const fn get_item_stack(&self) -> &Mutex<ItemStack> {
|
|
&self.item_stack
|
|
}
|
|
|
|
pub fn get_pickup_delay(&self) -> u8 {
|
|
self.pickup_delay.load(Ordering::Relaxed)
|
|
}
|
|
|
|
pub fn set_pickup_delay(&self, pickup_delay: u8) {
|
|
self.pickup_delay.store(pickup_delay, Ordering::Relaxed);
|
|
}
|
|
|
|
pub const fn get_entity(&self) -> &Entity {
|
|
&self.entity
|
|
}
|
|
|
|
pub fn can_merge(&self) -> bool {
|
|
let Ok(item_stack) = self.item_stack.try_lock() else {
|
|
return false;
|
|
};
|
|
|
|
item_stack.item_count < item_stack.get_max_stack_size()
|
|
}
|
|
|
|
pub fn try_merge(&self) {
|
|
if !self.can_merge() || self.never_despawn.load(Ordering::Relaxed) {
|
|
return;
|
|
}
|
|
|
|
let bounding_box = self.entity.bounding_box.load().expand(0.5, 0.0, 0.5);
|
|
|
|
let world = self.entity.world.load();
|
|
let entities = world.entities.load();
|
|
let items: Vec<Arc<Self>> = entities
|
|
.iter()
|
|
.filter_map(|entity: &Arc<dyn EntityBase>| {
|
|
entity.clone().get_item_entity().filter(|item| {
|
|
item.entity.entity_id != self.entity.entity_id
|
|
&& !item.never_despawn.load(Ordering::Relaxed)
|
|
&& item.entity.bounding_box.load().intersects(&bounding_box)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
for item in items {
|
|
if item.can_merge() {
|
|
if let Some(this_base) = world.get_entity_by_id(self.entity.entity_id)
|
|
&& let Some(this_item) = this_base.get_item_entity()
|
|
{
|
|
this_item.try_merge_with(&item);
|
|
}
|
|
|
|
if self.entity.removed.load(Ordering::SeqCst) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn try_merge_with(&self, other: &Self) {
|
|
// Always lock in entity_id order to prevent deadlock when two
|
|
// items try to merge with each other concurrently.
|
|
let (low, high) = if self.entity.entity_id < other.entity.entity_id {
|
|
(self, other)
|
|
} else {
|
|
(other, self)
|
|
};
|
|
|
|
let low_stack = low
|
|
.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
let high_stack = high
|
|
.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
|
|
let (self_stack, other_stack) = if self.entity.entity_id < other.entity.entity_id {
|
|
(low_stack, high_stack)
|
|
} else {
|
|
(high_stack, low_stack)
|
|
};
|
|
|
|
if !self_stack.are_equal(&other_stack)
|
|
|| self_stack.item_count + other_stack.item_count > self_stack.get_max_stack_size()
|
|
{
|
|
return;
|
|
}
|
|
|
|
let (target, mut stack1, source, mut stack2) =
|
|
if other_stack.item_count < self_stack.item_count {
|
|
(self, self_stack, other, other_stack)
|
|
} else {
|
|
(other, other_stack, self, self_stack)
|
|
};
|
|
|
|
let mut event = crate::plugin::api::events::entity::item_merge::ItemMergeEvent {
|
|
entity_id: target.entity.entity_id,
|
|
target_id: source.entity.entity_id,
|
|
cancelled: false,
|
|
};
|
|
if let Some(server) = self.entity.world.load().server.upgrade() {
|
|
server.plugin_manager.fire_blocking(&server, &mut event);
|
|
}
|
|
if event.cancelled {
|
|
return;
|
|
}
|
|
|
|
// Vanilla code adds a .min(64). Not needed with Vanilla item data
|
|
|
|
let max_size = stack1.get_max_stack_size();
|
|
|
|
let j = stack2.item_count.min(max_size - stack1.item_count);
|
|
|
|
stack1.increment(j);
|
|
|
|
stack2.decrement(j);
|
|
|
|
let empty1 = stack1.item_count == 0;
|
|
|
|
let empty2 = stack2.item_count == 0;
|
|
|
|
drop(stack1);
|
|
|
|
drop(stack2);
|
|
|
|
let never_despawn = source.never_despawn.load(Ordering::Relaxed);
|
|
|
|
target.never_despawn.store(never_despawn, Ordering::Relaxed);
|
|
|
|
if !never_despawn {
|
|
let age = target
|
|
.item_age
|
|
.load(Ordering::Relaxed)
|
|
.min(source.item_age.load(Ordering::Relaxed));
|
|
|
|
target.item_age.store(age, Ordering::Relaxed);
|
|
}
|
|
|
|
let never_pickup = source.never_pickup.load(Ordering::Relaxed);
|
|
|
|
target.never_pickup.store(never_pickup, Ordering::Relaxed);
|
|
|
|
if !never_pickup {
|
|
let source_delay = source.pickup_delay.load(Ordering::Relaxed);
|
|
target
|
|
.pickup_delay
|
|
.fetch_max(source_delay, Ordering::Relaxed);
|
|
}
|
|
|
|
if empty1 {
|
|
target.entity.remove();
|
|
} else {
|
|
target.init_data_tracker();
|
|
}
|
|
|
|
if empty2 {
|
|
source.entity.remove();
|
|
} else {
|
|
source.init_data_tracker();
|
|
}
|
|
}
|
|
|
|
fn decrement_pickup_delay(&self) {
|
|
self.pickup_delay
|
|
.try_update(Ordering::Relaxed, Ordering::Relaxed, |val| {
|
|
Some(val.saturating_sub(1))
|
|
})
|
|
.ok();
|
|
}
|
|
|
|
fn apply_fluid_drag_or_gravity(&self, mut velo: Vector3<f64>) -> Vector3<f64> {
|
|
let entity = &self.entity;
|
|
|
|
if entity.touching_water.load(Ordering::SeqCst) && entity.water_height.load() > 0.1 {
|
|
velo.x *= 0.99;
|
|
velo.z *= 0.99;
|
|
if velo.y < 0.06 {
|
|
velo.y += 5.0e-4;
|
|
}
|
|
} else if entity.touching_lava.load(Ordering::SeqCst) && entity.lava_height.load() > 0.1 {
|
|
velo.x *= 0.95;
|
|
velo.z *= 0.95;
|
|
if velo.y < 0.06 {
|
|
velo.y += 5.0e-4;
|
|
}
|
|
} else {
|
|
velo.y -= <Self as EntityBase>::get_gravity(self);
|
|
}
|
|
|
|
velo
|
|
}
|
|
|
|
fn update_no_physics_and_push_out(&self) {
|
|
let entity = &self.entity;
|
|
let pos = entity.pos.load();
|
|
let bounding_box = entity.bounding_box.load();
|
|
|
|
let no_physics = !entity
|
|
.world
|
|
.load()
|
|
.is_space_empty(bounding_box.expand(-1.0e-7, -1.0e-7, -1.0e-7));
|
|
|
|
entity.no_physics.store(no_physics, Ordering::Relaxed);
|
|
|
|
if no_physics {
|
|
entity.push_out_of_blocks(Vector3::new(
|
|
pos.x,
|
|
f64::midpoint(bounding_box.min.y, bounding_box.max.y),
|
|
pos.z,
|
|
));
|
|
}
|
|
}
|
|
|
|
fn should_tick_move(&self, move_velo: Vector3<f64>) -> Option<bool> {
|
|
let entity = &self.entity;
|
|
|
|
let mut tick_move = !entity.on_ground.load(Ordering::SeqCst)
|
|
|| move_velo.horizontal_length_squared() > 1.0e-5;
|
|
|
|
if !tick_move {
|
|
let Ok(item_age) = i32::try_from(self.item_age.load(Ordering::Relaxed)) else {
|
|
entity.remove();
|
|
return None;
|
|
};
|
|
|
|
tick_move = (item_age + entity.entity_id) % 4 == 0;
|
|
}
|
|
|
|
Some(tick_move)
|
|
}
|
|
|
|
fn move_and_apply_friction(
|
|
&self,
|
|
caller: &Arc<dyn EntityBase>,
|
|
server: &Server,
|
|
move_velo: Vector3<f64>,
|
|
) {
|
|
let entity = &self.entity;
|
|
|
|
entity.move_entity(caller, move_velo);
|
|
entity.tick_block_collisions(caller, server);
|
|
|
|
let mut friction = 0.98;
|
|
let on_ground = entity.on_ground.load(Ordering::SeqCst);
|
|
|
|
let mut velo = entity.velocity.load();
|
|
if on_ground {
|
|
let block_affecting_velo = entity.get_block_with_y_offset(0.999_999).1;
|
|
friction *= f64::from(block_affecting_velo.slipperiness) * 0.98;
|
|
}
|
|
|
|
velo = velo.multiply(friction, 0.98, friction);
|
|
|
|
if on_ground && velo.y < 0.0 {
|
|
velo.y = 0.0;
|
|
}
|
|
|
|
entity.velocity.store(velo);
|
|
}
|
|
|
|
fn process_age_and_merge(&self) -> bool {
|
|
if self.never_despawn.load(Ordering::Relaxed) {
|
|
return true;
|
|
}
|
|
|
|
let entity = &self.entity;
|
|
let age = self.item_age.fetch_add(1, Ordering::Relaxed) + 1;
|
|
|
|
if age >= 6000 {
|
|
let entity_id = entity.entity_id;
|
|
let world = entity.world.load_full();
|
|
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;
|
|
}
|
|
|
|
let n = if entity
|
|
.last_pos
|
|
.load()
|
|
.sub(&entity.pos.load())
|
|
.length_squared()
|
|
== 0.0
|
|
{
|
|
40
|
|
} else {
|
|
2
|
|
};
|
|
|
|
if age.is_multiple_of(n) && self.can_merge() {
|
|
self.try_merge();
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
fn sync_motion_if_dirty(&self, caller: &Arc<dyn EntityBase>, original_velo: Vector3<f64>) {
|
|
let entity = &self.entity;
|
|
|
|
entity.update_fluid_state(caller);
|
|
|
|
let velocity_dirty = entity.velocity_dirty.swap(false, Ordering::SeqCst)
|
|
|| entity.touching_water.load(Ordering::SeqCst)
|
|
|| entity.touching_lava.load(Ordering::SeqCst)
|
|
|| entity.velocity.load().sub(&original_velo).length_squared() > 0.1;
|
|
let moved = entity.pos.load() != entity.last_sent_pos.load();
|
|
let position_dirty = moved
|
|
&& self
|
|
.item_age
|
|
.load(Ordering::Relaxed)
|
|
.is_multiple_of(ITEM_UPDATE_INTERVAL);
|
|
|
|
if position_dirty || velocity_dirty {
|
|
entity.send_pos_rot();
|
|
} else if moved {
|
|
entity.send_bedrock_pos();
|
|
}
|
|
if velocity_dirty {
|
|
entity.send_velocity();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl EntityBase for ItemEntity {
|
|
fn tick(&self, caller: &Arc<dyn EntityBase>, server: &Server) {
|
|
let entity = &self.entity;
|
|
self.decrement_pickup_delay();
|
|
|
|
let original_velo = entity.velocity.load();
|
|
entity
|
|
.velocity
|
|
.store(self.apply_fluid_drag_or_gravity(original_velo));
|
|
|
|
self.update_no_physics_and_push_out();
|
|
|
|
let move_velo = entity.velocity.load(); // In case push_out_of_blocks modifies it
|
|
|
|
let Some(tick_move) = self.should_tick_move(move_velo) else {
|
|
return;
|
|
};
|
|
|
|
if tick_move {
|
|
self.move_and_apply_friction(caller, server, move_velo);
|
|
}
|
|
|
|
if self.process_age_and_merge() {
|
|
self.sync_motion_if_dirty(caller, original_velo);
|
|
}
|
|
}
|
|
|
|
fn init_data_tracker(&self) {
|
|
self.entity.send_meta_data(
|
|
&[Metadata::new(
|
|
pumpkin_data::tracked_data::item::ITEM,
|
|
&ItemStackSerializer::from(
|
|
self.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.clone(),
|
|
),
|
|
)],
|
|
None,
|
|
);
|
|
}
|
|
|
|
fn damage_with_context(
|
|
&self,
|
|
_caller: &dyn EntityBase,
|
|
amount: f32,
|
|
damage_type: DamageType,
|
|
_position: Option<Vector3<f64>>,
|
|
_source: Option<&dyn EntityBase>,
|
|
_cause: Option<&dyn EntityBase>,
|
|
) -> bool {
|
|
// Check if entity is fire_immune
|
|
let is_fire_damage = damage_type == DamageType::IN_FIRE
|
|
|| damage_type == DamageType::ON_FIRE
|
|
|| damage_type == DamageType::LAVA;
|
|
if is_fire_damage && self.entity.fire_immune.load(Ordering::Relaxed) {
|
|
return false;
|
|
}
|
|
|
|
loop {
|
|
let current = self.health.load(Relaxed);
|
|
let new = current - amount;
|
|
if self
|
|
.health
|
|
.compare_exchange(current, new, AcqRel, Relaxed)
|
|
.is_ok()
|
|
{
|
|
if new <= 0.0 {
|
|
self.entity.remove();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_player_collision(&self, player: &Arc<Player>) {
|
|
if self.pickup_delay.load(Ordering::Relaxed) > 0
|
|
|| player.living_entity.health.load() <= 0.0
|
|
|| player.is_spectator()
|
|
{
|
|
return;
|
|
}
|
|
|
|
let (item_id, count_before) = {
|
|
let stack = self
|
|
.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
(stack.item.id, stack.item_count)
|
|
};
|
|
|
|
let mut local_stack = self
|
|
.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.clone();
|
|
let inserted = player.inventory.insert_stack_anywhere(&mut local_stack);
|
|
let count_after = local_stack.item_count;
|
|
let is_empty = local_stack.is_empty();
|
|
*self
|
|
.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner) = local_stack;
|
|
|
|
if inserted || player.is_creative() {
|
|
player.inventory_changed.store(true, Ordering::Relaxed);
|
|
|
|
let amount_picked_up = if player.is_creative() {
|
|
count_before
|
|
} else {
|
|
count_before - count_after
|
|
};
|
|
|
|
if amount_picked_up > 0 {
|
|
player.increment_stat(
|
|
StatisticCategory::PickedUp,
|
|
item_id as i32,
|
|
amount_picked_up as i32,
|
|
);
|
|
}
|
|
|
|
player
|
|
.living_entity
|
|
.pickup(&self.entity, amount_picked_up.into());
|
|
|
|
let player_clone = player.clone();
|
|
tokio::spawn(async move {
|
|
player_clone
|
|
.current_screen_handler
|
|
.lock()
|
|
.await
|
|
.lock()
|
|
.await
|
|
.send_content_updates()
|
|
.await;
|
|
});
|
|
|
|
if is_empty {
|
|
self.entity.remove();
|
|
} else {
|
|
self.init_data_tracker();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_entity(&self) -> &Entity {
|
|
&self.entity
|
|
}
|
|
|
|
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
|
None
|
|
}
|
|
|
|
fn get_item_entity(self: Arc<Self>) -> Option<Arc<ItemEntity>> {
|
|
Some(self)
|
|
}
|
|
|
|
fn get_gravity(&self) -> f64 {
|
|
0.04
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
fn cast_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn send_bedrock_spawn_packet<'a>(
|
|
&'a self,
|
|
client: &'a crate::net::bedrock::BedrockClient,
|
|
) -> EntityBaseFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let entity = &self.entity;
|
|
let runtime_id = entity.entity_id as u64;
|
|
let data = {
|
|
let item_stack = self
|
|
.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
let packet = CAddItemActor {
|
|
target_actor_id: VarLong(runtime_id as i64),
|
|
target_runtime_id: VarULong(runtime_id),
|
|
item: ItemStackWrapper::from(&*item_stack),
|
|
position: entity.pos.load().to_f32_lossy(),
|
|
velocity: entity.velocity.load().to_f32_lossy(),
|
|
entity_data: entity.bedrock_metadata(),
|
|
is_from_fishing: false,
|
|
};
|
|
client.serialize_packet(&packet).ok()
|
|
};
|
|
if let Some(data) = data {
|
|
client.send_game_packet(data).await;
|
|
}
|
|
})
|
|
}
|
|
|
|
fn send_java_spawn_packet<'a>(
|
|
&'a self,
|
|
client: &'a crate::net::java::JavaClient,
|
|
) -> EntityBaseFuture<'a, ()> {
|
|
Box::pin(async move {
|
|
let spawn_packet = self.entity.create_spawn_packet();
|
|
if let Ok(data) = client.serialize_packet(&spawn_packet) {
|
|
client.enqueue_packet(data).await;
|
|
}
|
|
|
|
if client.version.load() >= CURRENT_MC_VERSION {
|
|
let metadata = Metadata::new(
|
|
pumpkin_data::tracked_data::item::ITEM,
|
|
ItemStackSerializer::from(
|
|
self.item_stack
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.clone(),
|
|
),
|
|
);
|
|
let mut data = Vec::new();
|
|
if metadata.write(&mut data, &client.version.load()).is_ok() {
|
|
data.push(255);
|
|
let meta_packet =
|
|
CSetEntityMetadata::new(self.entity.entity_id.into(), data.into());
|
|
if let Ok(meta_data) = client.serialize_packet(&meta_packet) {
|
|
client.enqueue_packet(meta_data).await;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|