diff --git a/pumpkin/src/entity/projectile/ender_pearl.rs b/pumpkin/src/entity/projectile/ender_pearl.rs new file mode 100644 index 000000000..a2aa5482e --- /dev/null +++ b/pumpkin/src/entity/projectile/ender_pearl.rs @@ -0,0 +1,137 @@ +use std::sync::Arc; +use std::sync::atomic::AtomicBool; + +use crate::{ + entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, projectile::ThrownItemEntity}, + server::Server, +}; +use pumpkin_data::entity::EntityStatus; +use pumpkin_data::particle::Particle; +use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_util::math::vector3::Vector3; + +pub struct EnderPearlEntity { + pub thrown: ThrownItemEntity, +} + +impl EnderPearlEntity { + pub async fn new(entity: Entity) -> Self { + entity.set_velocity(Vector3::new(0.0, 0.1, 0.0)).await; + + let thrown = ThrownItemEntity { + entity, + owner_id: None, + collides_with_projectiles: false, + has_hit: AtomicBool::new(false), + }; + + Self { thrown } + } + + pub async fn new_shot(entity: Entity, shooter: &Entity) -> Self { + let thrown = ThrownItemEntity::new(entity, shooter); + thrown + .entity + .set_velocity(Vector3::new(0.0, 0.1, 0.0)) + .await; + Self { thrown } + } +} + +impl NBTStorage for EnderPearlEntity {} + +impl EntityBase for EnderPearlEntity { + fn tick<'a>( + &'a self, + caller: 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 cast_any(&self) -> &dyn std::any::Any { + self + } + + fn on_hit(&self, hit: crate::entity::projectile::ProjectileHit) -> EntityBaseFuture<'_, ()> { + Box::pin(async move { + let entity = self.get_entity(); + let world = entity.world.load(); + + // Spawn portal particles at hit position + let hit_pos = hit.hit_pos(); + for _ in 0..32 { + let offset = Vector3::new( + rand::random::() as f64 - 0.5, + rand::random::() as f64 * 2.0, + rand::random::() as f64 - 0.5, + ); + let speed = Vector3::new( + rand::random::() - 0.5, + 0.0, + rand::random::() - 0.5, + ); + + world + .spawn_particle( + hit_pos.add(&offset), + Vector3::new(speed.x as f32, speed.y as f32, speed.z as f32), + 1.0, + 1, + Particle::Portal, + ) + .await; + } + + if let Some(owner_id) = self.thrown.owner_id + && let Some(owner) = world.get_entity_by_id(owner_id) + { + // Teleport owner to pearl's position before hit (approx) + let teleport_pos = entity.pos.load(); + + // In vanilla, teleport handles everything including sound + owner + .clone() + .teleport( + teleport_pos, + Some(owner.get_entity().yaw.load()), + Some(owner.get_entity().pitch.load()), + world.clone(), + ) + .await; + + // Play teleport sound at new position + world + .play_sound( + Sound::EntityPlayerTeleport, + SoundCategory::Players, + &teleport_pos, + ) + .await; + + // Deal 5 damage to owner + owner + .damage( + owner.as_ref(), + 5.0, + pumpkin_data::damage::DamageType::ENDER_PEARL, + ) + .await; + } + + world.send_entity_status(entity, EntityStatus::Death).await; + }) + } +} diff --git a/pumpkin/src/entity/projectile/mod.rs b/pumpkin/src/entity/projectile/mod.rs index 462226959..e8c8b283d 100644 --- a/pumpkin/src/entity/projectile/mod.rs +++ b/pumpkin/src/entity/projectile/mod.rs @@ -11,6 +11,7 @@ use std::{ }; pub mod arrow; pub mod egg; +pub mod ender_pearl; pub mod eye_of_ender; pub mod firework_rocket; pub mod lingering_potion; @@ -27,6 +28,7 @@ pub fn is_projectile(entity_type: &EntityType) -> bool { || *entity_type == EntityType::WIND_CHARGE || *entity_type == EntityType::SPLASH_POTION || *entity_type == EntityType::LINGERING_POTION + || *entity_type == EntityType::ENDER_PEARL } pub struct ThrownItemEntity { diff --git a/pumpkin/src/entity/type.rs b/pumpkin/src/entity/type.rs index 0ed6ca10b..956a03779 100644 --- a/pumpkin/src/entity/type.rs +++ b/pumpkin/src/entity/type.rs @@ -7,6 +7,9 @@ use uuid::Uuid; use crate::entity::boss::ender_dragon::EnderDragonEntity; use crate::entity::mob::blaze::BlazeEntity; use crate::entity::mob::zombie::zombie_villager::ZombieVillagerEntity; +use crate::entity::projectile::egg::EggEntity; +use crate::entity::projectile::ender_pearl::EnderPearlEntity; +use crate::entity::projectile::snowball::SnowballEntity; use crate::{ entity::{ Entity, EntityBase, @@ -80,6 +83,9 @@ pub async fn from_type( id if id == EntityType::ARMOR_STAND.id => Arc::new(ArmorStandEntity::new(entity)), id if id == EntityType::PAINTING.id => Arc::new(PaintingEntity::new(entity)), id if id == EntityType::END_CRYSTAL.id => Arc::new(EndCrystalEntity::new(entity)), + id if id == EntityType::ENDER_PEARL.id => Arc::new(EnderPearlEntity::new(entity).await), + id if id == EntityType::SNOWBALL.id => Arc::new(SnowballEntity::new(entity).await), + id if id == EntityType::EGG.id => Arc::new(EggEntity::new(entity).await), id if id == EntityType::SILVERFISH.id => SilverfishEntity::new(entity).await, id if id == EntityType::SLIME.id => SlimeEntity::new(entity), // Fallback Entity diff --git a/pumpkin/src/item/items/egg.rs b/pumpkin/src/item/items/egg.rs index d89c60dc1..51a250e09 100644 --- a/pumpkin/src/item/items/egg.rs +++ b/pumpkin/src/item/items/egg.rs @@ -51,6 +51,19 @@ impl ItemBehaviour for EggItem { egg.thrown .set_velocity_from(&player.living_entity.entity, pitch, yaw, 0.0, POWER, 1.0); world.spawn_entity(Arc::new(egg)).await; + + // Consume item + let held_item = player.inventory.held_item(); + let mut main_hand = held_item.lock().await; + if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) { + main_hand.decrement_unless_creative(player.gamemode.load(), 1); + } else { + let off_hand_item = player.inventory.off_hand_item().await; + let mut off_hand = off_hand_item.lock().await; + if !off_hand.is_empty() && Self::ids().contains(&off_hand.item.id) { + off_hand.decrement_unless_creative(player.gamemode.load(), 1); + } + } }) } diff --git a/pumpkin/src/item/items/ender_pearl.rs b/pumpkin/src/item/items/ender_pearl.rs new file mode 100644 index 000000000..b196b2e0e --- /dev/null +++ b/pumpkin/src/item/items/ender_pearl.rs @@ -0,0 +1,71 @@ +use std::pin::Pin; +use std::sync::Arc; + +use crate::entity::Entity; +use crate::entity::player::Player; +use crate::entity::projectile::ender_pearl::EnderPearlEntity; +use crate::item::{ItemBehaviour, ItemMetadata}; +use pumpkin_data::entity::EntityType; +use pumpkin_data::item::Item; +use pumpkin_data::sound::Sound; + +pub struct EnderPearlItem; + +impl ItemMetadata for EnderPearlItem { + fn ids() -> Box<[u16]> { + [Item::ENDER_PEARL.id].into() + } +} + +const POWER: f32 = 1.5; + +impl ItemBehaviour for EnderPearlItem { + fn normal_use<'a>( + &'a self, + _item: &'a Item, + player: &'a Player, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + let position = player.position(); + let world = player.world(); + world + .play_sound( + Sound::EntityEnderPearlThrow, + pumpkin_data::sound::SoundCategory::Neutral, + &position, + ) + .await; + + let entity = Entity::new(world.clone(), position, &EntityType::ENDER_PEARL); + let pearl = EnderPearlEntity::new_shot(entity, &player.living_entity.entity).await; + let yaw = player.living_entity.entity.yaw.load(); + let pitch = player.living_entity.entity.pitch.load(); + pearl.thrown.set_velocity_from( + &player.living_entity.entity, + pitch, + yaw, + 0.0, + POWER, + 1.0, + ); + world.spawn_entity(Arc::new(pearl)).await; + + // Consume item + let held_item = player.inventory.held_item(); + let mut main_hand = held_item.lock().await; + if !main_hand.is_empty() && main_hand.item.id == Item::ENDER_PEARL.id { + main_hand.decrement_unless_creative(player.gamemode.load(), 1); + } else { + let off_hand_item = player.inventory.off_hand_item().await; + let mut off_hand = off_hand_item.lock().await; + if !off_hand.is_empty() && off_hand.item.id == Item::ENDER_PEARL.id { + off_hand.decrement_unless_creative(player.gamemode.load(), 1); + } + } + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/pumpkin/src/item/items/mod.rs b/pumpkin/src/item/items/mod.rs index 90c3d7108..30832ec86 100644 --- a/pumpkin/src/item/items/mod.rs +++ b/pumpkin/src/item/items/mod.rs @@ -8,6 +8,7 @@ pub mod dye; pub mod egg; pub mod end_crystal; pub mod ender_eye; +pub mod ender_pearl; pub mod firework_rocket; pub mod glowing_ink_sac; pub mod hoe; @@ -43,6 +44,7 @@ use bucket::{EmptyBucketItem, FilledBucketItem}; use dye::DyeItem; use egg::EggItem; use ender_eye::EnderEyeItem; +use ender_pearl::EnderPearlItem; use glowing_ink_sac::GlowingInkSacItem; use hoe::HoeItem; use honeycomb::HoneyCombItem; @@ -82,6 +84,7 @@ pub fn default_registry() -> Arc { manager.register(HoneyCombItem); manager.register(NameTagItem); manager.register(EnderEyeItem); + manager.register(EnderPearlItem); manager.register(FireChargeItem); manager.register(DyeItem); manager.register(FireworkRocketItem); diff --git a/pumpkin/src/item/items/snowball.rs b/pumpkin/src/item/items/snowball.rs index 7cf14ad8a..3ca1acdd1 100644 --- a/pumpkin/src/item/items/snowball.rs +++ b/pumpkin/src/item/items/snowball.rs @@ -48,6 +48,19 @@ impl ItemBehaviour for SnowBallItem { 1.0, ); world.spawn_entity(Arc::new(snowball)).await; + + // Consume item + let held_item = player.inventory.held_item(); + let mut main_hand = held_item.lock().await; + if !main_hand.is_empty() && main_hand.item.id == Item::SNOWBALL.id { + main_hand.decrement_unless_creative(player.gamemode.load(), 1); + } else { + let off_hand_item = player.inventory.off_hand_item().await; + let mut off_hand = off_hand_item.lock().await; + if !off_hand.is_empty() && off_hand.item.id == Item::SNOWBALL.id { + off_hand.decrement_unless_creative(player.gamemode.load(), 1); + } + } }) }