//! Specialized typed wrappers for major Minecraft mob entities. //! //! Provides typed accessors and ergonomic helpers (`Sheep::from_entity`, `Wolf::from_mob`, etc.) //! without needing dozens of WIT resources. use std::ops::Deref; pub use crate::wit::pumpkin::plugin::uuid::Uuid; pub use crate::wit::pumpkin::plugin::world::{ AgeableData, BlockDirection, CatData, CreeperData, DyeColor, EndermanData, Entity, FoxData, IronGolemData, LivingEntity, Mob, MobData, SheepData, ShulkerData, SlimeData, VillagerData, VillagerProfession, WolfData, ZombieData, }; /// Trait implemented by all specialized mob wrappers to allow generic downcasting via `.cast::()`. pub trait MobCast<'a>: Sized { /// Attempts to wrap a [`Mob`] reference if the underlying entity data matches. fn from_mob(mob: &'a Mob) -> Option; /// Attempts to wrap an [`Entity`] reference if it is an AI mob matching this type. fn from_entity(entity: &'a Entity) -> Option { let mob = entity.as_mob()?; // Note: as_mob() returns a new Resource handle, so we extract via MobData check. Self::from_mob_owned(mob) } /// Attempts to wrap a [`LivingEntity`] reference if it is an AI mob matching this type. fn from_living(living: &'a LivingEntity) -> Option { let mob = living.as_mob()?; Self::from_mob_owned(mob) } #[doc(hidden)] fn from_mob_owned(mob: Mob) -> Option; } macro_rules! define_mob_wrapper { ( $(#[$meta:meta])* $name:ident, $variant:ident, $data_ty:ident ) => { $(#[$meta])* pub struct $name<'a> { mob: &'a Mob, _owned: Option, } impl<'a> $name<'a> { /// Wraps a borrowed [`Mob`] reference if this mob is of the matching type. #[must_use] pub fn from_mob(mob: &'a Mob) -> Option { if matches!(mob.get_mob_data(), MobData::$variant(_)) { Some(Self { mob, _owned: None }) } else { None } } /// Wraps a borrowed [`Entity`] reference if it is an AI mob of the matching type. #[must_use] pub fn from_entity(entity: &'a Entity) -> Option { let mob = entity.as_mob()?; Self::from_mob_owned(mob) } /// Wraps a borrowed [`LivingEntity`] reference if it is an AI mob of the matching type. #[must_use] pub fn from_living(living: &'a LivingEntity) -> Option { let mob = living.as_mob()?; Self::from_mob_owned(mob) } fn from_mob_owned(mob: Mob) -> Option { if matches!(mob.get_mob_data(), MobData::$variant(_)) { // Safe reference extension to the owned Mob let mob_ref = unsafe { &*(&mob as *const Mob) }; Some(Self { mob: mob_ref, _owned: Some(mob), }) } else { None } } /// Retrieves the underlying data record for this mob. #[must_use] pub fn get_data(&self) -> Option<$data_ty> { match self.mob.get_mob_data() { MobData::$variant(data) => Some(data), _ => None, } } /// Updates the underlying data record for this mob. pub fn set_data(&self, data: $data_ty) -> bool { self.mob.set_mob_data(MobData::$variant(data)) } } impl<'a> Deref for $name<'a> { type Target = Mob; fn deref(&self) -> &Self::Target { self.mob } } impl<'a> TryFrom<&'a Mob> for $name<'a> { type Error = (); fn try_from(mob: &'a Mob) -> Result { Self::from_mob(mob).ok_or(()) } } impl<'a> TryFrom<&'a Entity> for $name<'a> { type Error = (); fn try_from(entity: &'a Entity) -> Result { Self::from_entity(entity).ok_or(()) } } impl<'a> TryFrom<&'a LivingEntity> for $name<'a> { type Error = (); fn try_from(living: &'a LivingEntity) -> Result { Self::from_living(living).ok_or(()) } } impl<'a> MobCast<'a> for $name<'a> { fn from_mob(mob: &'a Mob) -> Option { Self::from_mob(mob) } fn from_living(living: &'a LivingEntity) -> Option { Self::from_living(living) } fn from_mob_owned(mob: Mob) -> Option { Self::from_mob_owned(mob) } } }; } define_mob_wrapper!( /// Specialized wrapper for Sheep entities. Sheep, Sheep, SheepData ); impl<'a> Sheep<'a> { /// Gets the fleece dye color of this sheep. #[must_use] pub fn get_color(&self) -> DyeColor { self.get_data().map_or(DyeColor::White, |d| d.color) } /// Sets the fleece dye color of this sheep. pub fn set_color(&self, color: DyeColor) { if let Some(mut data) = self.get_data() { data.color = color; self.set_data(data); } } /// Returns whether this sheep has been sheared. #[must_use] pub fn is_sheared(&self) -> bool { self.get_data().is_some_and(|d| d.is_sheared) } /// Sets whether this sheep is sheared. pub fn set_sheared(&self, sheared: bool) { if let Some(mut data) = self.get_data() { data.is_sheared = sheared; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Wolf entities. Wolf, Wolf, WolfData ); impl<'a> Wolf<'a> { /// Returns whether this wolf is tamed. #[must_use] pub fn is_tamed(&self) -> bool { self.get_data().is_some_and(|d| d.is_tamed) } /// Sets whether this wolf is tamed. pub fn set_tamed(&self, tamed: bool) { if let Some(mut data) = self.get_data() { data.is_tamed = tamed; self.set_data(data); } } /// Gets the UUID of the player who owns this wolf, if any. #[must_use] pub fn get_owner(&self) -> Option { self.get_data().and_then(|d| d.owner) } /// Sets the owner of this wolf by UUID. pub fn set_owner(&self, owner: Option) { if let Some(mut data) = self.get_data() { data.owner = owner; self.set_data(data); } } /// Returns whether this wolf is currently in a sitting pose. #[must_use] pub fn is_sitting(&self) -> bool { self.get_data().is_some_and(|d| d.is_sitting) } /// Orders this wolf to sit or stand. pub fn set_sitting(&self, sitting: bool) { if let Some(mut data) = self.get_data() { data.is_sitting = sitting; self.set_data(data); } } /// Gets the collar dye color of this wolf. #[must_use] pub fn get_collar_color(&self) -> DyeColor { self.get_data().map_or(DyeColor::Red, |d| d.collar_color) } /// Sets the collar dye color of this wolf. pub fn set_collar_color(&self, color: DyeColor) { if let Some(mut data) = self.get_data() { data.collar_color = color; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Cat entities. Cat, Cat, CatData ); impl<'a> Cat<'a> { /// Returns whether this cat is tamed. #[must_use] pub fn is_tamed(&self) -> bool { self.get_data().is_some_and(|d| d.is_tamed) } /// Sets whether this cat is tamed. pub fn set_tamed(&self, tamed: bool) { if let Some(mut data) = self.get_data() { data.is_tamed = tamed; self.set_data(data); } } /// Gets the owner UUID of this cat. #[must_use] pub fn get_owner(&self) -> Option { self.get_data().and_then(|d| d.owner) } /// Sets the owner UUID of this cat. pub fn set_owner(&self, owner: Option) { if let Some(mut data) = self.get_data() { data.owner = owner; self.set_data(data); } } /// Returns whether this cat is in a sitting pose. #[must_use] pub fn is_sitting(&self) -> bool { self.get_data().is_some_and(|d| d.is_sitting) } /// Orders this cat to sit or stand. pub fn set_sitting(&self, sitting: bool) { if let Some(mut data) = self.get_data() { data.is_sitting = sitting; self.set_data(data); } } /// Gets the collar dye color of this cat. #[must_use] pub fn get_collar_color(&self) -> DyeColor { self.get_data().map_or(DyeColor::Red, |d| d.collar_color) } /// Sets the collar dye color of this cat. pub fn set_collar_color(&self, color: DyeColor) { if let Some(mut data) = self.get_data() { data.collar_color = color; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Villager entities. Villager, Villager, VillagerData ); impl<'a> Villager<'a> { /// Gets the profession of this villager. #[must_use] pub fn get_profession(&self) -> VillagerProfession { self.get_data() .map_or(VillagerProfession::None, |d| d.profession) } /// Sets the profession of this villager. pub fn set_profession(&self, profession: VillagerProfession) { if let Some(mut data) = self.get_data() { data.profession = profession; self.set_data(data); } } /// Gets the trading level of this villager (1-5). #[must_use] pub fn get_level(&self) -> u8 { self.get_data().map_or(1, |d| d.level) } /// Sets the trading level of this villager (1-5). pub fn set_level(&self, level: u8) { if let Some(mut data) = self.get_data() { data.level = level; self.set_data(data); } } /// Gets the trading experience points of this villager. #[must_use] pub fn get_experience(&self) -> u32 { self.get_data().map_or(0, |d| d.experience) } /// Sets the trading experience points of this villager. pub fn set_experience(&self, experience: u32) { if let Some(mut data) = self.get_data() { data.experience = experience; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Creeper entities. Creeper, Creeper, CreeperData ); impl<'a> Creeper<'a> { /// Returns whether this creeper is charged (struck by lightning). #[must_use] pub fn is_powered(&self) -> bool { self.get_data().is_some_and(|d| d.is_powered) } /// Sets whether this creeper is powered/charged. pub fn set_powered(&self, powered: bool) { if let Some(mut data) = self.get_data() { data.is_powered = powered; self.set_data(data); } } /// Gets the fuse duration of this creeper in ticks. #[must_use] pub fn get_fuse(&self) -> i32 { self.get_data().map_or(30, |d| d.fuse) } /// Sets the fuse duration of this creeper in ticks. pub fn set_fuse(&self, fuse: i32) { if let Some(mut data) = self.get_data() { data.fuse = fuse; self.set_data(data); } } /// Returns whether this creeper has been manually ignited with flint and steel. #[must_use] pub fn is_ignited(&self) -> bool { self.get_data().is_some_and(|d| d.is_ignited) } /// Sets whether this creeper is ignited. pub fn set_ignited(&self, ignited: bool) { if let Some(mut data) = self.get_data() { data.is_ignited = ignited; self.set_data(data); } } /// Gets the explosion radius of this creeper. #[must_use] pub fn get_explosion_radius(&self) -> u8 { self.get_data().map_or(3, |d| d.explosion_radius) } /// Sets the explosion radius of this creeper. pub fn set_explosion_radius(&self, radius: u8) { if let Some(mut data) = self.get_data() { data.explosion_radius = radius; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Slime and Magma Cube entities. Slime, Slime, SlimeData ); impl<'a> Slime<'a> { /// Gets the size scale of this slime. #[must_use] pub fn get_size(&self) -> i32 { self.get_data().map_or(1, |d| d.size) } /// Sets the size scale of this slime. pub fn set_size(&self, size: i32) { if let Some(mut data) = self.get_data() { data.size = size; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Enderman entities. Enderman, Enderman, EndermanData ); impl<'a> Enderman<'a> { /// Gets the numerical block state ID carried by this enderman, if any. #[must_use] pub fn get_carried_block(&self) -> Option { self.get_data().and_then(|d| d.carried_block_state) } /// Sets the carried block state ID for this enderman. pub fn set_carried_block(&self, block_state: Option) { if let Some(mut data) = self.get_data() { data.carried_block_state = block_state; self.set_data(data); } } /// Returns whether this enderman is currently screaming (angry). #[must_use] pub fn is_screaming(&self) -> bool { self.get_data().is_some_and(|d| d.is_screaming) } /// Returns whether this enderman is staring at a player. #[must_use] pub fn is_staring(&self) -> bool { self.get_data().is_some_and(|d| d.is_staring) } } define_mob_wrapper!( /// Specialized wrapper for Iron Golem entities. IronGolem, IronGolem, IronGolemData ); impl<'a> IronGolem<'a> { /// Returns whether this iron golem was created by a player. #[must_use] pub fn is_player_created(&self) -> bool { self.get_data().is_some_and(|d| d.is_player_created) } /// Sets whether this iron golem is considered player-created. pub fn set_player_created(&self, created: bool) { if let Some(mut data) = self.get_data() { data.is_player_created = created; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Fox entities. Fox, Fox, FoxData ); impl<'a> Fox<'a> { /// Returns whether this fox is sitting. #[must_use] pub fn is_sitting(&self) -> bool { self.get_data().is_some_and(|d| d.is_sitting) } /// Sets whether this fox is sitting. pub fn set_sitting(&self, sitting: bool) { if let Some(mut data) = self.get_data() { data.is_sitting = sitting; self.set_data(data); } } /// Returns whether this fox is sleeping. #[must_use] pub fn is_sleeping(&self) -> bool { self.get_data().is_some_and(|d| d.is_sleeping) } /// Sets whether this fox is sleeping. pub fn set_sleeping(&self, sleeping: bool) { if let Some(mut data) = self.get_data() { data.is_sleeping = sleeping; self.set_data(data); } } /// Returns whether this fox is crouching. #[must_use] pub fn is_crouching(&self) -> bool { self.get_data().is_some_and(|d| d.is_crouching) } /// Sets whether this fox is crouching. pub fn set_crouching(&self, crouching: bool) { if let Some(mut data) = self.get_data() { data.is_crouching = crouching; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Shulker entities. Shulker, Shulker, ShulkerData ); impl<'a> Shulker<'a> { /// Gets the attached block direction of this shulker. #[must_use] pub fn get_attached_face(&self) -> BlockDirection { self.get_data() .map_or(BlockDirection::Down, |d| d.attached_face) } /// Sets the attached block direction of this shulker. pub fn set_attached_face(&self, face: BlockDirection) { if let Some(mut data) = self.get_data() { data.attached_face = face; self.set_data(data); } } /// Gets the raw peek amount of this shulker (0-100). #[must_use] pub fn get_peek_amount(&self) -> u8 { self.get_data().map_or(0, |d| d.peek_amount) } /// Sets the raw peek amount of this shulker (0-100). pub fn set_peek_amount(&self, amount: u8) { if let Some(mut data) = self.get_data() { data.peek_amount = amount; self.set_data(data); } } /// Gets the custom dye color of this shulker, if dyed. #[must_use] pub fn get_color(&self) -> Option { self.get_data().and_then(|d| d.color) } /// Sets or removes the custom dye color of this shulker. pub fn set_color(&self, color: Option) { if let Some(mut data) = self.get_data() { data.color = color; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Zombie entities. Zombie, Zombie, ZombieData ); impl<'a> Zombie<'a> { /// Returns whether this zombie is a baby. #[must_use] pub fn is_baby(&self) -> bool { self.get_data().is_some_and(|d| d.is_baby) } /// Sets whether this zombie is a baby. pub fn set_baby(&self, baby: bool) { if let Some(mut data) = self.get_data() { data.is_baby = baby; self.set_data(data); } } /// Returns whether this zombie is capable of breaking doors. #[must_use] pub fn can_break_doors(&self) -> bool { self.get_data().is_some_and(|d| d.can_break_doors) } /// Sets whether this zombie can break doors. pub fn set_can_break_doors(&self, can_break: bool) { if let Some(mut data) = self.get_data() { data.can_break_doors = can_break; self.set_data(data); } } } define_mob_wrapper!( /// Specialized wrapper for Ageable animal mobs (cows, pigs, chickens, rabbits, etc.). Ageable, Ageable, AgeableData ); impl<'a> Ageable<'a> { /// Returns whether this animal is a baby. #[must_use] pub fn is_baby(&self) -> bool { self.get_data().is_some_and(|d| d.is_baby) } /// Sets whether this animal is a baby. pub fn set_baby(&self, baby: bool) { if let Some(mut data) = self.get_data() { data.is_baby = baby; self.set_data(data); } } /// Gets the age of this animal in ticks (negative for babies). #[must_use] pub fn get_age(&self) -> i32 { self.get_data().map_or(0, |d| d.age) } /// Sets the age of this animal in ticks. pub fn set_age(&self, age: i32) { if let Some(mut data) = self.get_data() { data.age = age; self.set_data(data); } } } /// Extension trait providing generic `.cast::()` downcasting on [`Entity`] and [`Mob`]. pub trait EntityCastExt { /// Attempts to cast this entity or mob reference to a specialized mob wrapper type. fn cast<'a, T: MobCast<'a>>(&'a self) -> Option; } impl EntityCastExt for Mob { fn cast<'a, T: MobCast<'a>>(&'a self) -> Option { T::from_mob(self) } } impl EntityCastExt for Entity { fn cast<'a, T: MobCast<'a>>(&'a self) -> Option { T::from_entity(self) } } impl EntityCastExt for LivingEntity { fn cast<'a, T: MobCast<'a>>(&'a self) -> Option { T::from_living(self) } }