diff --git a/pumpkin/src/command/args/resource/item.rs b/pumpkin/src/command/args/resource/item.rs index 8e5358a77..6afd1156a 100644 --- a/pumpkin/src/command/args/resource/item.rs +++ b/pumpkin/src/command/args/resource/item.rs @@ -1,7 +1,6 @@ use pumpkin_data::item_stack::ItemStack; use pumpkin_data::{ data_component::DataComponent, - data_component_impl::DataComponentImpl, item::Item, tag::{RegistryKey, get_tag_ids}, }; @@ -120,27 +119,15 @@ impl<'a> FindArg<'a> for ItemArgumentConsumer { ) { // Match the DataComponent key - if let Some(data_comp) = DataComponent::try_from_name(comp_key) - { - // Handle Profile or other data components - match data_comp { - DataComponent::Profile => { - if let Some(profile_impl) = pumpkin_data::data_component_impl::ProfileImpl::read_data(&nbt_tag) { - patch.push((data_comp, Some(profile_impl.to_dyn()))); - } - } - DataComponent::CustomData => { - if let pumpkin_nbt::tag::NbtTag::Compound(compound) = nbt_tag { - patch.push((data_comp, Some(pumpkin_data::data_component_impl::CustomDataImpl { data: compound }.to_dyn()))); - } - } - DataComponent::CustomName => { - if let pumpkin_nbt::tag::NbtTag::String(text_str) = nbt_tag { - patch.push((data_comp, Some(pumpkin_data::data_component_impl::CustomNameImpl { name: pumpkin_util::text::TextComponent::text(String::from(text_str)) }.to_dyn()))); - } - } - _ => {} - } + if let (Some(data_comp), Some(comp_impl)) = ( + DataComponent::try_from_name(comp_key), + pumpkin_data::data_component_impl::read_data( + DataComponent::try_from_name(comp_key) + .unwrap_or(DataComponent::CustomData), + &nbt_tag, + ), + ) { + patch.push((data_comp, Some(comp_impl))); } } } diff --git a/pumpkin/src/entity/ageable.rs b/pumpkin/src/entity/ageable.rs new file mode 100644 index 000000000..79a7f6ef6 --- /dev/null +++ b/pumpkin/src/entity/ageable.rs @@ -0,0 +1,141 @@ +use pumpkin_data::meta_data_type::MetaDataType; +use pumpkin_data::tracked_data::TrackedData; +use pumpkin_protocol::java::client::play::Metadata; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering::Relaxed}; + +use crate::entity::mob::Mob; + +pub const BABY_START_AGE: i32 = -24000; +pub const FORCED_AGE_PARTICLE_TICKS: i32 = 40; + +pub struct AgeableData { + pub forced_age: AtomicI32, + pub forced_age_timer: AtomicI32, + pub age_locked: AtomicBool, + pub age_lock_particle_timer: AtomicI32, +} + +impl Default for AgeableData { + fn default() -> Self { + Self { + forced_age: AtomicI32::new(0), + forced_age_timer: AtomicI32::new(0), + age_locked: AtomicBool::new(false), + age_lock_particle_timer: AtomicI32::new(0), + } + } +} + +pub trait AgeableMob: Mob { + fn get_ageable_data(&self) -> &AgeableData; + + fn get_baby_start_age(&self) -> i32 { + BABY_START_AGE + } + + fn is_baby(&self) -> bool { + self.get_mob_entity().living_entity.entity.age.load(Relaxed) < 0 + } + + fn set_baby(&self, baby: bool) { + self.set_age(if baby { self.get_baby_start_age() } else { 0 }); + } + + fn get_age(&self) -> i32 { + self.get_mob_entity().living_entity.entity.age.load(Relaxed) + } + + fn set_age(&self, new_age: i32) { + let mob = self.get_mob_entity(); + let entity = &mob.living_entity.entity; + let old_age = entity.age.swap(new_age, Relaxed); + + if (old_age < 0 && new_age >= 0) || (old_age >= 0 && new_age < 0) { + let is_baby = new_age < 0; + entity.send_meta_data( + &[Metadata::new( + TrackedData::BABY_ID, + MetaDataType::BOOLEAN, + is_baby, + )], + None, + ); + } + } + + fn is_age_locked(&self) -> bool { + self.get_ageable_data().age_locked.load(Relaxed) + } + + fn set_age_locked(&self, locked: bool) { + self.get_ageable_data().age_locked.store(locked, Relaxed); + } + + fn can_age_up(&self) -> bool { + self.is_baby() && !self.is_age_locked() + } + + fn age_up(&self, seconds: i32, forced: bool) { + let mut age = self.get_age(); + let old_age = age; + age += seconds * 20; + if age > 0 { + age = 0; + } + + let delta = age - old_age; + self.set_age(age); + + let data = self.get_ageable_data(); + if forced { + data.forced_age.fetch_add(delta, Relaxed); + if data.forced_age_timer.load(Relaxed) == 0 { + data.forced_age_timer.store(40, Relaxed); + } + } + + if self.get_age() == 0 { + self.set_age(data.forced_age.load(Relaxed)); + } + } + + #[must_use] + fn get_speed_up_seconds_when_feeding(ticks_until_adult: i32) -> i32 { + (ticks_until_adult as f32 / 20.0 * 0.1) as i32 + } + + fn write_ageable_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) { + if self.can_be_a_baby() { + nbt.put_int("Age", self.get_age()); + nbt.put_int( + "ForcedAge", + self.get_ageable_data().forced_age.load(Relaxed), + ); + nbt.put_bool("AgeLocked", self.is_age_locked()); + } + } + + fn read_ageable_nbt(&self, nbt: &pumpkin_nbt::compound::NbtCompound) { + if self.can_be_a_baby() { + self.set_age(nbt.get_int("Age").unwrap_or(0)); + self.get_ageable_data() + .forced_age + .store(nbt.get_int("ForcedAge").unwrap_or(0), Relaxed); + self.set_age_locked(nbt.get_bool("AgeLocked").unwrap_or(false)); + } + } + + fn can_be_a_baby(&self) -> bool { + true + } + + fn ageable_ai_step(&self) { + if self.can_age_up() { + let age = self.get_age() + 1; + self.set_age(age); + } else if self.get_age() > 0 { + let age = self.get_age() - 1; + self.set_age(age); + } + } +} diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index 2960a6616..36af954a6 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -601,7 +601,19 @@ impl EntityBase for T { } if mob_entity.love_ticks.load(Relaxed) > 0 { - mob_entity.love_ticks.fetch_sub(1, Relaxed); + let ticks = mob_entity.love_ticks.fetch_sub(1, Relaxed); + if ticks % 10 == 0 { + let entity = &mob_entity.living_entity.entity; + let pos = entity.pos.load(); + let world = entity.world.load(); + world.spawn_particle( + pos + Vector3::new(0.0, f64::from(entity.height()) + 0.5, 0.0), + Vector3::new(0.5, 0.5, 0.5), + 1.0, + 1, + pumpkin_data::particle::Particle::Heart, + ); + } } self.mob_tick(caller).await; diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index c457611ab..9e9cc025d 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -80,6 +80,7 @@ use std::sync::{ use tokio::sync::Mutex; use uuid::Uuid; +pub mod ageable; pub mod ai; pub mod area_effect_cloud; pub mod attributes; diff --git a/pumpkin/src/entity/passive/animal.rs b/pumpkin/src/entity/passive/animal.rs new file mode 100644 index 000000000..c884823fa --- /dev/null +++ b/pumpkin/src/entity/passive/animal.rs @@ -0,0 +1,105 @@ +use std::sync::Arc; + +use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::particle::Particle; +use pumpkin_data::sound::{Sound, SoundCategory}; + +use crate::entity::{EntityBaseFuture, mob::Mob, player::Player}; +use pumpkin_util::math::vector3::Vector3; + +pub trait Animal: Mob { + fn is_food(&self, item_stack: &ItemStack) -> bool; + + fn play_eating_sound(&self, sound: Sound) { + let mob_entity = self.get_mob_entity(); + let entity = &mob_entity.living_entity.entity; + let world = entity.world.load(); + world.play_sound(sound, SoundCategory::Neutral, &entity.pos.load()); + } + + fn write_animal_nbt(&self, nbt: &mut pumpkin_nbt::compound::NbtCompound) { + let mob_entity = self.get_mob_entity(); + let in_love = mob_entity + .love_ticks + .load(std::sync::atomic::Ordering::Relaxed); + nbt.put_int("InLove", in_love); + if let Some(uuid) = mob_entity.breeder.load() { + nbt.put_uuid("LoveCause", uuid); + } + } + + fn read_animal_nbt(&self, nbt: &pumpkin_nbt::compound::NbtCompound) { + let mob_entity = self.get_mob_entity(); + let in_love = nbt.get_int("InLove").unwrap_or(0); + let love_cause = nbt.get_uuid("LoveCause"); + mob_entity.set_love_ticks(in_love, love_cause); + } + + fn animal_interact<'a>( + &'a self, + player: &'a Arc, + item_stack: &'a mut ItemStack, + ambient_sound: Sound, + ) -> EntityBaseFuture<'a, bool> { + Box::pin(async move { + let mob_entity = self.get_mob_entity(); + if self.is_food(item_stack) { + let age = mob_entity + .living_entity + .entity + .age + .load(std::sync::atomic::Ordering::Relaxed); + + if age >= 0 && mob_entity.is_breeding_ready() && !mob_entity.is_in_love() { + item_stack.decrement_unless_creative(player.gamemode.load(), 1); + + mob_entity.set_love_ticks(600, Some(player.gameprofile.id)); + let entity = &mob_entity.living_entity.entity; + let world = entity.world.load(); + let pos = entity.pos.load(); + + world.send_entity_status( + entity, + pumpkin_data::entity::EntityStatus::InLoveHearts, + ); + + world.spawn_particle( + pos + Vector3::new(0.0, f64::from(entity.height()), 0.0), + Vector3::new(0.5, 0.5, 0.5), + 1.0, + 7, + Particle::Heart, + ); + world.play_sound(ambient_sound, SoundCategory::Neutral, &entity.pos.load()); + return true; + } + + if age < 0 { + item_stack.decrement_unless_creative(player.gamemode.load(), 1); + let speedup = (-age / 10).max(1); + mob_entity + .living_entity + .entity + .age + .fetch_add(speedup, std::sync::atomic::Ordering::Relaxed); + + let entity = &mob_entity.living_entity.entity; + let world = entity.world.load(); + let pos = entity.pos.load(); + + world.spawn_particle( + pos + Vector3::new(0.0, f64::from(entity.height()), 0.0), + Vector3::new(0.5, 0.5, 0.5), + 1.0, + 7, + Particle::HappyVillager, + ); + self.play_eating_sound(ambient_sound); + return true; + } + } + + mob_entity.mob_interact(player, item_stack).await + }) + } +} diff --git a/pumpkin/src/entity/passive/chicken.rs b/pumpkin/src/entity/passive/chicken.rs index b042643f8..2746ed51e 100644 --- a/pumpkin/src/entity/passive/chicken.rs +++ b/pumpkin/src/entity/passive/chicken.rs @@ -5,22 +5,22 @@ use std::sync::{ use pumpkin_data::item_stack::ItemStack; use pumpkin_data::meta_data_type::MetaDataType; -use pumpkin_data::particle::Particle; -use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::sound::Sound; use pumpkin_data::tracked_data::TrackedData; use pumpkin_data::{entity::EntityType, item::Item}; use pumpkin_protocol::codec::var_int::VarInt; -use pumpkin_util::math::vector3::Vector3; use rand::RngExt; use crate::entity::{ Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture, + ageable::AgeableMob, ai::goal::{ breed::BreedGoal, escape_danger::EscapeDangerGoal, follow_parent::FollowParentGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal, tempt::TemptGoal, wander_around::WanderAroundGoal, }, mob::{Mob, MobEntity}, + passive::animal::Animal, player::Player, }; use pumpkin_nbt::compound::NbtCompound; @@ -41,6 +41,7 @@ pub struct ChickenEntity { pub mob_entity: MobEntity, pub variant: AtomicU8, egg_lay_time: AtomicI32, + pub ageable_data: crate::entity::ageable::AgeableData, } impl ChickenEntity { @@ -51,6 +52,7 @@ impl ChickenEntity { mob_entity, variant: AtomicU8::new(1), // Default to temperate egg_lay_time: AtomicI32::new(egg_lay_time), + ageable_data: crate::entity::ageable::AgeableData::default(), }; let mob_arc = Arc::new(chicken); let mob_weak: Weak = { @@ -78,10 +80,18 @@ impl ChickenEntity { } } +impl crate::entity::ageable::AgeableMob for ChickenEntity { + fn get_ageable_data(&self) -> &crate::entity::ageable::AgeableData { + &self.ageable_data + } +} + impl NBTStorage for ChickenEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.write_nbt(nbt).await; + self.write_ageable_nbt(nbt); + self.write_animal_nbt(nbt); nbt.put_int("EggLayTime", self.egg_lay_time.load(Ordering::Relaxed)); let variant_str = match self.variant.load(Ordering::Relaxed) { 0 => "minecraft:cold", @@ -95,6 +105,8 @@ impl NBTStorage for ChickenEntity { fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { Box::pin(async { self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; + self.read_ageable_nbt(nbt); + self.read_animal_nbt(nbt); self.egg_lay_time .store(nbt.get_int("EggLayTime").unwrap_or(6000), Ordering::Relaxed); if let Some(variant_str) = nbt.get_string("variant") { @@ -112,6 +124,12 @@ impl NBTStorage for ChickenEntity { } } +impl super::animal::Animal for ChickenEntity { + fn is_food(&self, item_stack: &ItemStack) -> bool { + TEMPT_ITEMS.iter().any(|i| i.id == item_stack.item.id) + } +} + impl Mob for ChickenEntity { fn get_mob_entity(&self) -> &MobEntity { &self.mob_entity @@ -172,32 +190,7 @@ impl Mob for ChickenEntity { player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { - Box::pin(async move { - let is_food = TEMPT_ITEMS.iter().any(|i| i.id == item_stack.item.id); - if is_food && self.is_breeding_ready() && !self.is_in_love() { - item_stack.decrement_unless_creative(player.gamemode.load(), 1); - - self.mob_entity - .set_love_ticks(600, Some(player.gameprofile.id)); - let entity = &self.mob_entity.living_entity.entity; - let world = entity.world.load(); - let pos = entity.pos.load(); - - world.spawn_particle( - pos + Vector3::new(0.0, f64::from(entity.height()), 0.0), - Vector3::new(0.5, 0.5, 0.5), - 1.0, - 7, - Particle::Heart, - ); - world.play_sound( - Sound::EntityChickenAmbient, - SoundCategory::Neutral, - &entity.pos.load(), - ); - return true; - } - self.mob_entity.mob_interact(player, item_stack).await - }) + use super::animal::Animal; + self.animal_interact(player, item_stack, Sound::EntityChickenAmbient) } } diff --git a/pumpkin/src/entity/passive/cow.rs b/pumpkin/src/entity/passive/cow.rs index 848fb2c73..69221d283 100644 --- a/pumpkin/src/entity/passive/cow.rs +++ b/pumpkin/src/entity/passive/cow.rs @@ -1,19 +1,19 @@ use std::sync::{Arc, Weak}; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::particle::Particle; -use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::sound::Sound; use pumpkin_data::{entity::EntityType, item::Item}; -use pumpkin_util::math::vector3::Vector3; use crate::entity::{ - Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture, + Entity, EntityBaseFuture, NBTStorage, NbtFuture, + ageable::AgeableMob, ai::goal::{ breed::BreedGoal, escape_danger::EscapeDangerGoal, follow_parent::FollowParentGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal, tempt::TemptGoal, wander_around::WanderAroundGoal, }, mob::{Mob, MobEntity}, + passive::animal::Animal, player::Player, }; use pumpkin_nbt::compound::NbtCompound; @@ -25,12 +25,16 @@ const TEMPT_ITEMS: &[&Item] = &[&Item::WHEAT]; /// Wiki: pub struct CowEntity { pub mob_entity: MobEntity, + pub ageable_data: crate::entity::ageable::AgeableData, } impl CowEntity { pub fn new(entity: Entity) -> Arc { let mob_entity = MobEntity::new(entity); - let cow = Self { mob_entity }; + let cow = Self { + mob_entity, + ageable_data: crate::entity::ageable::AgeableData::default(), + }; let mob_arc = Arc::new(cow); let mob_weak: Weak = { let mob_arc: Arc = mob_arc.clone(); @@ -57,13 +61,33 @@ impl CowEntity { } } +impl crate::entity::ageable::AgeableMob for CowEntity { + fn get_ageable_data(&self) -> &crate::entity::ageable::AgeableData { + &self.ageable_data + } +} + impl NBTStorage for CowEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { - self.mob_entity.living_entity.write_nbt(nbt) + Box::pin(async move { + self.mob_entity.living_entity.write_nbt(nbt).await; + self.write_ageable_nbt(nbt); + self.write_animal_nbt(nbt); + }) } fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { - self.mob_entity.living_entity.read_nbt_non_mut(nbt) + Box::pin(async move { + self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; + self.read_ageable_nbt(nbt); + self.read_animal_nbt(nbt); + }) + } +} + +impl super::animal::Animal for CowEntity { + fn is_food(&self, item_stack: &ItemStack) -> bool { + TEMPT_ITEMS.iter().any(|i| i.id == item_stack.item.id) } } @@ -77,32 +101,7 @@ impl Mob for CowEntity { player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { - Box::pin(async move { - let is_food = TEMPT_ITEMS.iter().any(|i| i.id == item_stack.item.id); - if is_food && self.is_breeding_ready() && !self.is_in_love() { - item_stack.decrement_unless_creative(player.gamemode.load(), 1); - - self.mob_entity - .set_love_ticks(600, Some(player.gameprofile.id)); - let entity = &self.mob_entity.living_entity.entity; - let world = entity.world.load(); - let pos = entity.pos.load(); - - world.spawn_particle( - pos + Vector3::new(0.0, f64::from(entity.height()), 0.0), - Vector3::new(0.5, 0.5, 0.5), - 1.0, - 7, - Particle::Heart, - ); - world.play_sound( - Sound::EntityCowAmbient, - SoundCategory::Neutral, - &entity.pos.load(), - ); - return true; - } - self.mob_entity.mob_interact(player, item_stack).await - }) + use super::animal::Animal; + self.animal_interact(player, item_stack, Sound::EntityCowAmbient) } } diff --git a/pumpkin/src/entity/passive/mod.rs b/pumpkin/src/entity/passive/mod.rs index f25536a6b..fb9417577 100644 --- a/pumpkin/src/entity/passive/mod.rs +++ b/pumpkin/src/entity/passive/mod.rs @@ -1,4 +1,5 @@ pub mod allay; +pub mod animal; pub mod armadillo; pub mod axolotl; pub mod bee; diff --git a/pumpkin/src/entity/passive/pig.rs b/pumpkin/src/entity/passive/pig.rs index a3008d649..bbb888b5b 100644 --- a/pumpkin/src/entity/passive/pig.rs +++ b/pumpkin/src/entity/passive/pig.rs @@ -1,19 +1,19 @@ use std::sync::{Arc, Weak}; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::particle::Particle; -use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_data::sound::Sound; use pumpkin_data::{entity::EntityType, item::Item}; -use pumpkin_util::math::vector3::Vector3; use crate::entity::{ - Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture, + Entity, EntityBaseFuture, NBTStorage, NbtFuture, + ageable::AgeableMob, ai::goal::{ breed::BreedGoal, escape_danger::EscapeDangerGoal, follow_parent::FollowParentGoal, look_around::RandomLookAroundGoal, look_at_entity::LookAtEntityGoal, swim::SwimGoal, tempt::TemptGoal, wander_around::WanderAroundGoal, }, mob::{Mob, MobEntity}, + passive::animal::Animal, player::Player, }; use pumpkin_nbt::compound::NbtCompound; @@ -30,12 +30,16 @@ const PIG_FOOD: &[&Item] = &[ /// Wiki: pub struct PigEntity { pub mob_entity: MobEntity, + pub ageable_data: crate::entity::ageable::AgeableData, } impl PigEntity { pub fn new(entity: Entity) -> Arc { let mob_entity = MobEntity::new(entity); - let pig = Self { mob_entity }; + let pig = Self { + mob_entity, + ageable_data: crate::entity::ageable::AgeableData::default(), + }; let mob_arc = Arc::new(pig); let mob_weak: Weak = { let mob_arc: Arc = mob_arc.clone(); @@ -62,13 +66,33 @@ impl PigEntity { } } +impl crate::entity::ageable::AgeableMob for PigEntity { + fn get_ageable_data(&self) -> &crate::entity::ageable::AgeableData { + &self.ageable_data + } +} + impl NBTStorage for PigEntity { fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) -> NbtFuture<'a, ()> { - self.mob_entity.living_entity.write_nbt(nbt) + Box::pin(async move { + self.mob_entity.living_entity.write_nbt(nbt).await; + self.write_ageable_nbt(nbt); + self.write_animal_nbt(nbt); + }) } fn read_nbt_non_mut<'a>(&'a self, nbt: &'a NbtCompound) -> NbtFuture<'a, ()> { - self.mob_entity.living_entity.read_nbt_non_mut(nbt) + Box::pin(async move { + self.mob_entity.living_entity.read_nbt_non_mut(nbt).await; + self.read_ageable_nbt(nbt); + self.read_animal_nbt(nbt); + }) + } +} + +impl super::animal::Animal for PigEntity { + fn is_food(&self, item_stack: &ItemStack) -> bool { + PIG_FOOD.iter().any(|i| i.id == item_stack.item.id) } } @@ -82,32 +106,7 @@ impl Mob for PigEntity { player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { - Box::pin(async move { - let is_food = PIG_FOOD.iter().any(|i| i.id == item_stack.item.id); - if is_food && self.is_breeding_ready() && !self.is_in_love() { - item_stack.decrement_unless_creative(player.gamemode.load(), 1); - - self.mob_entity - .set_love_ticks(600, Some(player.gameprofile.id)); - let entity = &self.mob_entity.living_entity.entity; - let world = entity.world.load(); - let pos = entity.pos.load(); - - world.spawn_particle( - pos + Vector3::new(0.0, f64::from(entity.height()), 0.0), - Vector3::new(0.5, 0.5, 0.5), - 1.0, - 7, - Particle::Heart, - ); - world.play_sound( - Sound::EntityPigAmbient, - SoundCategory::Neutral, - &entity.pos.load(), - ); - return true; - } - self.mob_entity.mob_interact(player, item_stack).await - }) + use super::animal::Animal; + self.animal_interact(player, item_stack, Sound::EntityPigAmbient) } } diff --git a/pumpkin/src/entity/passive/sheep.rs b/pumpkin/src/entity/passive/sheep.rs index 624183469..d047764b0 100644 --- a/pumpkin/src/entity/passive/sheep.rs +++ b/pumpkin/src/entity/passive/sheep.rs @@ -10,7 +10,7 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::java::client::play::Metadata; use crate::entity::{ - Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture, + Entity, EntityBaseFuture, NBTStorage, NbtFuture, ai::goal::{ breed::BreedGoal, eat_grass::EatGrassGoal, escape_danger::EscapeDangerGoal, follow_parent::FollowParentGoal, look_around::RandomLookAroundGoal, @@ -22,9 +22,7 @@ use crate::entity::{ }; use pumpkin_data::item_stack::ItemStack; -use pumpkin_data::particle::Particle; -use pumpkin_data::sound::{Sound, SoundCategory}; -use pumpkin_util::math::vector3::Vector3; +use pumpkin_data::sound::Sound; const TEMPT_ITEMS: &[&Item] = &[&Item::WHEAT]; @@ -129,6 +127,12 @@ impl NBTStorage for SheepEntity { } } +impl super::animal::Animal for SheepEntity { + fn is_food(&self, item_stack: &ItemStack) -> bool { + TEMPT_ITEMS.iter().any(|i| i.id == item_stack.item.id) + } +} + impl Mob for SheepEntity { fn get_mob_entity(&self) -> &MobEntity { &self.mob_entity @@ -145,32 +149,7 @@ impl Mob for SheepEntity { player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { - Box::pin(async move { - let is_food = TEMPT_ITEMS.iter().any(|i| i.id == item_stack.item.id); - if is_food && self.is_breeding_ready() && !self.is_in_love() { - item_stack.decrement_unless_creative(player.gamemode.load(), 1); - - self.mob_entity - .set_love_ticks(600, Some(player.gameprofile.id)); - let entity = &self.mob_entity.living_entity.entity; - let world = entity.world.load(); - let pos = entity.pos.load(); - - world.spawn_particle( - pos + Vector3::new(0.0, f64::from(entity.height()), 0.0), - Vector3::new(0.5, 0.5, 0.5), - 1.0, - 7, - Particle::Heart, - ); - world.play_sound( - Sound::EntitySheepAmbient, - SoundCategory::Neutral, - &entity.pos.load(), - ); - return true; - } - self.mob_entity.mob_interact(player, item_stack).await - }) + use super::animal::Animal; + self.animal_interact(player, item_stack, Sound::EntitySheepAmbient) } }