use std::sync::Arc; use std::sync::atomic::AtomicBool; use crate::plugin::player::egg_throw::PlayerEggThrowEvent; use crate::{ entity::{ Entity, EntityBase, EntityBaseFuture, NBTStorage, projectile::ThrownItemEntity, r#type::from_type, }, server::Server, }; use pumpkin_data::entity::{EntityStatus, EntityType}; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; use pumpkin_data::meta_data_type::MetaDataType; use pumpkin_data::tracked_data::TrackedData; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::java::client::play::Metadata; use pumpkin_util::math::vector3::Vector3; use tokio::sync::RwLock; use uuid::Uuid; const MAX_EGG_HATCH_EVENT_SPAWNS: usize = 16; const GRAVITY: f64 = 0.03; pub struct EggEntity { pub thrown: ThrownItemEntity, pub item_stack: RwLock, } impl EggEntity { pub fn new(entity: Entity) -> Self { // Default velocity slightly upward for thrown egg entity.set_velocity(Vector3::new(0.0, 0.1, 0.0)); let thrown = ThrownItemEntity { entity, owner_id: None, collides_with_projectiles: false, has_hit: AtomicBool::new(false), gravity: GRAVITY, }; Self { thrown, item_stack: RwLock::new(ItemStack::new(1, &Item::EGG)), } } pub fn new_shot(entity: Entity, shooter: &Entity) -> Self { let thrown = ThrownItemEntity::new(entity, shooter, GRAVITY); // Default slight upward velocity thrown.entity.set_velocity(Vector3::new(0.0, 0.1, 0.0)); Self { thrown, item_stack: RwLock::new(ItemStack::new(1, &Item::EGG)), } } /// Set the item stack shown by this thrown egg pub async fn set_item_stack(&self, item_stack: ItemStack) { let mut write = self.item_stack.write().await; *write = item_stack; } } impl NBTStorage for EggEntity {} impl EntityBase for EggEntity { fn init_data_tracker(&self) -> EntityBaseFuture<'_, ()> { Box::pin(async move { let entity = self.get_entity(); let stack = self.item_stack.read().await; // Sync the item stack so the client renders the correct color/variant entity.send_meta_data( &[Metadata::new( TrackedData::ITEM_STACK, MetaDataType::ITEM_STACK, &ItemStackSerializer::from(stack.clone()), )], None, ); }) } fn tick<'a>( &'a self, caller: &'a Arc, server: &'a Server, ) -> EntityBaseFuture<'a, ()> { Box::pin(async move { self.thrown.process_tick(caller, server).await }) } fn get_entity(&self) -> &Entity { self.thrown.get_entity() } fn get_living_entity(&self) -> Option<&crate::entity::living::LivingEntity> { None } fn as_nbt_storage(&self) -> &dyn NBTStorage { self } fn on_hit(&self, hit: crate::entity::projectile::ProjectileHit) -> EntityBaseFuture<'_, ()> { Box::pin(async move { let world = self.get_entity().world.load(); let hit_pos = hit.hit_pos(); let normal = hit.normal(); // Chicken spawn position offset slightly from hit position let spawn_pos = hit_pos.add(&normal.multiply(0.5, 0.5, 0.5)); // Play egg break particles world.send_entity_status(self.get_entity(), EntityStatus::Death); // Decide spawn count per probabilities: // r == 0 -> spawn 4 (1/256) // r in 1..31 -> spawn 1 (31/256) // else -> 0 let r: u8 = rand::random(); // 0..=255 let mut to_spawn = if r == 0 { 4usize } else { usize::from(r < 32) }; let mut hatching = to_spawn > 0; let mut hatching_type: &'static EntityType = &EntityType::CHICKEN; if let Some(owner_id) = self.thrown.owner_id && let Some(player) = world.get_player_by_id(owner_id) && let Some(server) = world.server.upgrade() { let event = PlayerEggThrowEvent::new( player, self.get_entity().entity_uuid, hatching, to_spawn as u8, hatching_type, ); let event = server.plugin_manager.fire(event).await; if event.cancelled { hatching = false; } else { hatching = event.hatching; to_spawn = (event.num_hatches as usize).min(MAX_EGG_HATCH_EVENT_SPAWNS); hatching_type = event.hatching_type; } } // Spawn chickens in a separate task to prevent stack overflow if hatching && to_spawn > 0 { let world_clone = world.clone(); let spawn_pos_clone = spawn_pos; let variant_name = { let stack = self.item_stack.read().await; stack.get_data_component::() .map(|comp| comp.value.clone()) }; tokio::spawn(async move { for _ in 0..to_spawn { let mob = from_type(hatching_type, spawn_pos_clone, &world_clone, Uuid::new_v4()); let yaw = rand::random::() * 360.0; let new_entity = mob.get_entity(); new_entity.set_rotation(yaw, 0.0); new_entity.set_age(-24000); if let Some(name) = &variant_name { mob.set_variant_name(name); } world_clone.spawn_entity(mob).await; } }); } }) } fn cast_any(&self) -> &dyn std::any::Any { self } }