Pumpkin/pumpkin/src/entity/mob/mod.rs
2026-01-17 19:26:25 +01:00

238 lines
6.9 KiB
Rust

use super::{Entity, EntityBase, NBTStorage, ai::path::Navigator, living::LivingEntity};
use crate::entity::EntityBaseFuture;
use crate::entity::ai::control::look_control::LookControl;
use crate::entity::ai::goal::goal_selector::GoalSelector;
use crate::server::Server;
use crate::world::World;
use crossbeam::atomic::AtomicCell;
use pumpkin_data::damage::DamageType;
use pumpkin_data::meta_data_type::MetaDataType;
use pumpkin_data::tracked_data::TrackedData;
use pumpkin_protocol::java::client::play::Metadata;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering};
use tokio::sync::Mutex;
pub mod creeper;
pub mod drowned;
pub mod skeleton;
pub mod zombie;
pub struct MobEntity {
pub living_entity: LivingEntity,
pub goals_selector: Mutex<GoalSelector>,
pub target_selector: Mutex<GoalSelector>,
pub navigator: Mutex<Navigator>,
pub target: Mutex<Option<Arc<dyn EntityBase>>>,
pub look_control: Mutex<LookControl>,
pub position_target: AtomicCell<BlockPos>,
pub position_target_range: AtomicI32,
mob_flags: AtomicU8,
}
impl MobEntity {
#[expect(dead_code)]
const AI_DISABLED_FLAG: u8 = 1;
#[expect(dead_code)]
const LEFT_HANDED_FLAG: u8 = 2;
const ATTACKING_FLAG: u8 = 4;
#[must_use]
pub fn new(entity: Entity) -> Self {
Self {
living_entity: LivingEntity::new(entity),
goals_selector: Mutex::new(GoalSelector::default()),
target_selector: Mutex::new(GoalSelector::default()),
navigator: Mutex::new(Navigator::default()),
target: Mutex::new(None),
look_control: Mutex::new(LookControl::default()),
position_target: AtomicCell::new(BlockPos::ZERO),
position_target_range: AtomicI32::new(-1),
mob_flags: AtomicU8::new(0),
}
}
pub fn is_in_position_target_range(&self) -> bool {
self.is_in_position_target_range_pos(self.living_entity.entity.block_pos.load())
}
pub fn is_in_position_target_range_pos(&self, block_pos: BlockPos) -> bool {
let position_target_range = self.position_target_range.load(Relaxed);
if position_target_range == -1 {
true
} else {
self.position_target.load().squared_distance(block_pos)
< position_target_range * position_target_range
}
}
pub async fn set_attacking(&self, attacking: bool) {
self.set_mob_flag(Self::ATTACKING_FLAG, attacking).await
}
async fn set_mob_flag(&self, flag: u8, value: bool) {
let index = flag;
let mut b = self.mob_flags.load(Ordering::Relaxed);
if value {
b |= index;
} else {
b &= !index;
}
self.mob_flags.store(b, Ordering::Relaxed);
self.living_entity
.entity
.send_meta_data(&[Metadata::new(
TrackedData::DATA_MOB_FLAGS,
MetaDataType::Byte,
b,
)])
.await;
}
}
// This trait contains all overridable functions
pub trait Mob: EntityBase + Send + Sync {
fn get_random(&self) -> rand::rngs::ThreadRng {
rand::rng()
}
fn get_max_look_yaw_change(&self) -> f32 {
10.0
}
fn get_max_look_pitch_change(&self) -> f32 {
40.0
}
fn get_max_head_rotation(&self) -> f32 {
75.0
}
fn get_mob_entity(&self) -> &MobEntity;
fn get_path_aware_entity(&self) -> Option<&dyn PathAwareEntity> {
None
}
}
impl<T: Mob + Send + 'static> EntityBase for T {
fn tick<'a>(
&'a self,
caller: Arc<dyn EntityBase>,
server: &'a Server,
) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
let mob_entity = self.get_mob_entity();
mob_entity.living_entity.tick(caller, server).await;
let age = mob_entity.living_entity.entity.age.load(Relaxed);
if (age + mob_entity.living_entity.entity.entity_id) % 2 != 0 && age > 1 {
mob_entity
.target_selector
.lock()
.await
.tick_goals(self, false)
.await;
mob_entity
.goals_selector
.lock()
.await
.tick_goals(self, false)
.await;
} else {
mob_entity.target_selector.lock().await.tick(self).await;
mob_entity.goals_selector.lock().await.tick(self).await;
}
let mut navigator = mob_entity.navigator.lock().await;
navigator.tick(&mob_entity.living_entity).await;
drop(navigator);
let mut look_control = mob_entity.look_control.lock().await;
look_control.tick(self).await;
drop(look_control);
})
}
fn damage_with_context<'a>(
&'a self,
caller: &'a dyn EntityBase,
amount: f32,
damage_type: DamageType,
position: Option<Vector3<f64>>,
source: Option<&'a dyn EntityBase>,
cause: Option<&'a dyn EntityBase>,
) -> EntityBaseFuture<'a, bool> {
Box::pin(async move {
self.get_mob_entity()
.living_entity
.damage_with_context(caller, amount, damage_type, position, source, cause)
.await
})
}
fn get_entity(&self) -> &Entity {
&self.get_mob_entity().living_entity.entity
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
Some(&self.get_mob_entity().living_entity)
}
fn as_nbt_storage(&self) -> &dyn NBTStorage {
self
}
fn get_gravity(&self) -> f64 {
self.get_mob_entity().living_entity.get_gravity()
}
}
#[expect(dead_code)]
const DEFAULT_PATHFINDING_FAVOR: f32 = 0.0;
pub trait PathAwareEntity: Mob + Send + Sync {
fn get_pathfinding_favor(&self, _block_pos: BlockPos, _world: Arc<World>) -> f32 {
0.0
}
// TODO: missing SpawnReason attribute
fn can_spawn(&self, world: Arc<World>) -> bool {
self.get_pathfinding_favor(
self.get_mob_entity().living_entity.entity.block_pos.load(),
world,
) >= 0.0
}
fn is_navigation<'a>(&'a self) -> Pin<Box<dyn Future<Output = bool> + Send + 'a>> {
Box::pin(async {
let navigator = self.get_mob_entity().navigator.lock().await;
!navigator.is_idle()
})
}
// TODO: implement
fn is_panicking(&self) -> bool {
false
}
fn should_follow_leash(&self) -> bool {
true
}
fn on_short_leash_tick(&self) {
// TODO: implement
}
fn before_leash_tick(&self) {
// TODO: implement
}
fn get_follow_leash_speed(&self) -> f32 {
1.0
}
}