Pumpkin/crates/pumpkin/src/entity/passive/wolf.rs
2026-08-18 18:49:48 +02:00

304 lines
11 KiB
Rust

use std::sync::{
Arc, Weak,
atomic::{AtomicBool, AtomicU8, Ordering},
};
use crossbeam::atomic::AtomicCell;
use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::tag::{self, Taggable};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::java::client::play::Metadata;
use uuid::Uuid;
use crate::entity::{
Entity, EntityBase, EntityBaseFuture, NBTStorage, NbtFuture,
ageable::AgeableMob,
ai::goal::{
active_target::ActiveTargetGoal, avoid_entity::AvoidEntityGoal, beg::BegGoal,
breed::BreedGoal, escape_danger::EscapeDangerGoal, follow_owner::FollowOwnerGoal,
follow_parent::FollowParentGoal, look_around::RandomLookAroundGoal,
look_at_entity::LookAtEntityGoal, melee_attack::MeleeAttackGoal,
owner_hurt_by_target::OwnerHurtByTargetGoal, owner_hurt_target::OwnerHurtTargetGoal,
revenge::RevengeGoal, swim::SwimGoal, wander_around::WanderAroundGoal,
},
mob::{Mob, MobEntity},
passive::animal::Animal,
};
pub struct WolfEntity {
pub mob_entity: MobEntity,
pub variant: AtomicU8,
pub collar_color: AtomicU8,
pub is_tame: AtomicBool,
pub is_sitting: AtomicBool,
pub owner: AtomicCell<Option<Uuid>>,
pub ageable_data: crate::entity::ageable::AgeableData,
}
impl WolfEntity {
pub fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity);
let wolf = Self {
mob_entity,
variant: AtomicU8::new(3), // Default to pale
collar_color: AtomicU8::new(14), // Default to red
is_tame: AtomicBool::new(false),
is_sitting: AtomicBool::new(false),
owner: AtomicCell::new(None),
ageable_data: crate::entity::ageable::AgeableData::default(),
};
let mob_arc = Arc::new(wolf);
let mob_weak: Weak<dyn Mob> = {
let mob_arc: Arc<dyn Mob> = mob_arc.clone();
Arc::downgrade(&mob_arc)
};
{
let mut goal_selector = mob_arc
.mob_entity
.goals_selector
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
// Goal selector (matching Vanilla registerGoals):
// 1: SwimGoal (FloatGoal)
goal_selector.add_goal(1, Box::new(SwimGoal::default()));
// 1: EscapeDangerGoal (TamableAnimalPanicGoal)
goal_selector.add_goal(1, EscapeDangerGoal::new(1.5));
// 3: Avoid Llama
goal_selector.add_goal(
3,
Box::new(AvoidEntityGoal::new(&EntityType::LLAMA, 24.0, 1.5, 1.5)),
);
// 5: MeleeAttackGoal
goal_selector.add_goal(5, Box::new(MeleeAttackGoal::new(1.0, true)));
// 6: FollowOwnerGoal
goal_selector.add_goal(6, FollowOwnerGoal::new(1.0, 10.0, 2.0));
// 7: BreedGoal
goal_selector.add_goal(7, BreedGoal::new(1.0));
// 8: FollowParentGoal & WanderAroundGoal
goal_selector.add_goal(8, Box::new(FollowParentGoal::new(1.1)));
goal_selector.add_goal(8, Box::new(WanderAroundGoal::new(1.0)));
// 9: BegGoal
goal_selector.add_goal(9, BegGoal::new(8.0));
// 10: LookAtPlayer & RandomLookAround
goal_selector.add_goal(
10,
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0),
);
goal_selector.add_goal(10, Box::new(RandomLookAroundGoal::default()));
};
{
let mut target_selector = mob_arc
.mob_entity
.target_selector
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
// Target selector (matching Vanilla registerGoals):
// 1: OwnerHurtByTargetGoal
target_selector.add_goal(1, OwnerHurtByTargetGoal::new());
// 2: OwnerHurtTargetGoal
target_selector.add_goal(2, OwnerHurtTargetGoal::new());
// 3: HurtByTargetGoal (RevengeGoal)
target_selector.add_goal(3, Box::new(RevengeGoal::new(true)));
// 4: NearestAttackableTarget (Player)
target_selector.add_goal(
4,
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::PLAYER, true),
);
// 5: NonTameRandomTarget (Sheep, Rabbit, Fox)
target_selector.add_goal(
5,
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::SHEEP, false),
);
target_selector.add_goal(
5,
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::RABBIT, false),
);
target_selector.add_goal(
5,
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::FOX, false),
);
// 7: NearestAttackableTarget (Skeleton)
target_selector.add_goal(
7,
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::SKELETON, false),
);
};
mob_arc
}
pub fn get_tame_flags(&self) -> u8 {
let mut flags = 0u8;
if self.is_sitting.load(Ordering::Relaxed) {
flags |= 0x01;
}
if self.is_tame.load(Ordering::Relaxed) {
flags |= 0x04;
}
flags
}
}
impl AgeableMob for WolfEntity {
fn get_ageable_data(&self) -> &crate::entity::ageable::AgeableData {
&self.ageable_data
}
}
impl Animal for WolfEntity {
fn is_food(&self, item_stack: &ItemStack) -> bool {
let item = item_stack.get_item();
item.has_tag(&tag::Item::MINECRAFT_WOLF_FOOD) || item == &Item::BONE
}
}
impl NBTStorage for WolfEntity {
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);
let variant_str = match self.variant.load(Ordering::Relaxed) {
0 => "minecraft:ashen",
1 => "minecraft:black",
2 => "minecraft:chestnut",
4 => "minecraft:rusty",
5 => "minecraft:snowy",
6 => "minecraft:spotted",
7 => "minecraft:striped",
8 => "minecraft:woods",
_ => "minecraft:pale",
};
nbt.put_string("variant", variant_str.to_string());
nbt.put_byte(
"CollarColor",
self.collar_color.load(Ordering::Relaxed) as i8,
);
nbt.put_bool("IsTame", self.is_tame.load(Ordering::Relaxed));
nbt.put_bool("Sitting", self.is_sitting.load(Ordering::Relaxed));
if let Some(owner) = self.owner.load() {
nbt.put_uuid("Owner", owner);
}
})
}
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);
if let Some(variant_str) = nbt.get_string("variant") {
let variant = match variant_str
.strip_prefix("minecraft:")
.unwrap_or(variant_str)
{
"ashen" => 0,
"black" => 1,
"chestnut" => 2,
"rusty" => 4,
"snowy" => 5,
"spotted" => 6,
"striped" => 7,
"woods" => 8,
_ => 3,
};
self.variant.store(variant, Ordering::Relaxed);
}
if let Some(collar) = nbt.get_byte("CollarColor") {
self.collar_color.store(collar as u8, Ordering::Relaxed);
} else if let Some(collar_int) = nbt.get_int("CollarColor") {
self.collar_color.store(collar_int as u8, Ordering::Relaxed);
}
if let Some(sitting) = nbt.get_bool("Sitting") {
self.is_sitting.store(sitting, Ordering::Relaxed);
}
if let Some(owner) = nbt.get_uuid("Owner") {
self.owner.store(Some(owner));
self.is_tame.store(true, Ordering::Relaxed);
} else if let Some(is_tame) = nbt.get_bool("IsTame") {
self.is_tame.store(is_tame, Ordering::Relaxed);
}
})
}
}
impl Mob for WolfEntity {
fn get_mob_entity(&self) -> &MobEntity {
&self.mob_entity
}
fn get_owner_uuid(&self) -> Option<Uuid> {
self.owner.load()
}
fn is_sitting(&self) -> bool {
self.is_sitting.load(Ordering::Relaxed)
}
fn mob_set_variant_name(&self, name: &str) {
let variant = match name.strip_prefix("minecraft:").unwrap_or(name) {
"ashen" => 0,
"black" => 1,
"chestnut" => 2,
"rusty" => 4,
"snowy" => 5,
"spotted" => 6,
"striped" => 7,
"woods" => 8,
_ => 3,
};
self.variant.store(variant, Ordering::Relaxed);
}
fn mob_init_data_tracker(&self) -> EntityBaseFuture<'_, ()> {
Box::pin(async move {
let entity = self.get_entity();
let is_baby = entity.age.load(Ordering::Relaxed) < 0;
if is_baby {
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::BABY_ID,
true,
)],
None,
);
}
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::TAMEABLE_FLAGS,
self.get_tame_flags(),
)],
None,
);
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::COLLAR_COLOR,
VarInt(self.collar_color.load(Ordering::Relaxed) as i32),
)],
None,
);
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::WOLF_VARIANT_ID,
VarInt(self.variant.load(Ordering::Relaxed) as i32),
)],
None,
);
entity.send_meta_data(
&[Metadata::new(
pumpkin_data::tracked_data::wolf::OWNER_UUID,
self.owner.load(),
)],
None,
);
})
}
}