feat: some more entity AI work
This commit is contained in:
parent
ba13f8a6c7
commit
3fbefafe4d
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -103,7 +103,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -114,7 +114,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -391,11 +391,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.0.0"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -924,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2461,7 +2461,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2884,7 +2884,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3449,7 +3449,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3473,15 +3473,6 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
|
||||
@ -54,7 +54,7 @@ base64 = "0.22.1"
|
||||
bitflags = "2.10.0"
|
||||
cesu8 = "1.1"
|
||||
cfb8 = "0.8"
|
||||
colored = "3.0"
|
||||
colored = "3.1"
|
||||
console-subscriber = { version = "0.5.0", default-features = false }
|
||||
crc-fast = "1.9.0"
|
||||
criterion = { version = "0.8", default-features = false }
|
||||
|
||||
94
pumpkin/src/entity/ai/goal/creeper_ignite.rs
Normal file
94
pumpkin/src/entity/ai/goal/creeper_ignite.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use super::{Controls, Goal};
|
||||
use crate::entity::ai::goal::GoalFuture;
|
||||
use crate::entity::mob::Mob;
|
||||
use crate::entity::mob::creeper::CreeperEntity;
|
||||
|
||||
pub struct CreeperIgniteGoal {
|
||||
goal_control: Controls,
|
||||
creeper: Arc<CreeperEntity>,
|
||||
}
|
||||
|
||||
impl CreeperIgniteGoal {
|
||||
#[must_use]
|
||||
pub fn new(creeper: Arc<CreeperEntity>) -> Self {
|
||||
Self {
|
||||
goal_control: Controls::MOVE,
|
||||
creeper,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Goal for CreeperIgniteGoal {
|
||||
fn can_start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
let creeper = mob.get_mob_entity();
|
||||
let target_lock = creeper.target.lock().await;
|
||||
|
||||
if self.creeper.fuse_speed.load(Ordering::Relaxed) > 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(target) = target_lock.as_ref() {
|
||||
let dist_sq = mob
|
||||
.get_entity()
|
||||
.pos
|
||||
.load()
|
||||
.squared_distance_to_vec(target.get_entity().pos.load());
|
||||
return dist_sq < 9.0;
|
||||
}
|
||||
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
fn start<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let mut navigator = mob.get_mob_entity().navigator.lock().await;
|
||||
navigator.cancel();
|
||||
|
||||
// TODO
|
||||
})
|
||||
}
|
||||
|
||||
fn stop<'a>(&'a mut self, _mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
// TODO
|
||||
})
|
||||
}
|
||||
|
||||
fn tick<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let target_lock = mob.get_mob_entity().target.lock().await;
|
||||
|
||||
let Some(target) = target_lock.as_ref() else {
|
||||
self.creeper.set_fuse_speed(-1).await;
|
||||
return;
|
||||
};
|
||||
|
||||
let dist_sq = mob
|
||||
.get_entity()
|
||||
.pos
|
||||
.load()
|
||||
.squared_distance_to_vec(target.get_entity().pos.load());
|
||||
|
||||
if dist_sq > 49.0 {
|
||||
self.creeper.set_fuse_speed(-1).await;
|
||||
}
|
||||
// TODO: Check line of sight
|
||||
else {
|
||||
self.creeper.set_fuse_speed(1).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn should_run_every_tick(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn controls(&self) -> Controls {
|
||||
self.goal_control
|
||||
}
|
||||
}
|
||||
@ -120,18 +120,18 @@ impl Goal for MeleeAttackGoal {
|
||||
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async {
|
||||
let mut target = mob.get_mob_entity().target.lock().await;
|
||||
if target.is_none() {
|
||||
return;
|
||||
|
||||
if let Some(entity) = target.as_deref() {
|
||||
if !EntityPredicate::ExceptCreativeOrSpectator
|
||||
.test(entity.get_entity())
|
||||
.await
|
||||
{
|
||||
*target = None;
|
||||
}
|
||||
}
|
||||
|
||||
if !EntityPredicate::ExceptCreativeOrSpectator
|
||||
.test(mob.get_entity())
|
||||
.await
|
||||
{
|
||||
*target = None;
|
||||
}
|
||||
|
||||
// TODO: set attacking to false and stop navigation
|
||||
let mut navigator = mob.get_mob_entity().navigator.lock().await;
|
||||
navigator.cancel();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ use std::{any::TypeId, ops::BitOr, pin::Pin, ptr};
|
||||
|
||||
pub mod active_target;
|
||||
pub mod ambient_stand;
|
||||
pub mod creeper_ignite;
|
||||
pub mod goal_selector;
|
||||
pub mod look_around;
|
||||
pub mod look_at_entity;
|
||||
|
||||
@ -20,7 +20,7 @@ pub struct TrackTargetGoal {
|
||||
can_navigate_flag: i32,
|
||||
check_can_navigate_cooldown: i32,
|
||||
time_without_visibility: i32,
|
||||
max_time_without_visibility: i32, // Default 60
|
||||
max_time_without_visibility: i32,
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
@ -103,14 +103,12 @@ impl Goal for TrackTargetGoal {
|
||||
let mob = mob.get_mob_entity();
|
||||
let mob_target = mob.target.lock().await;
|
||||
|
||||
// We need to decide which target to use for the check
|
||||
let target = if mob_target.is_some() {
|
||||
(*mob_target).clone()
|
||||
} else {
|
||||
self.target.clone()
|
||||
};
|
||||
|
||||
// Drop the guard immediately after access to release the lock
|
||||
drop(mob_target);
|
||||
|
||||
if target.is_none() {
|
||||
@ -125,17 +123,14 @@ impl Goal for TrackTargetGoal {
|
||||
self.can_navigate_flag = 0;
|
||||
self.check_can_navigate_cooldown = 0;
|
||||
self.time_without_visibility = 0;
|
||||
// No await needed here
|
||||
})
|
||||
}
|
||||
|
||||
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async {
|
||||
let mob = mob.get_mob_entity();
|
||||
|
||||
let mut mob_target = mob.target.lock().await;
|
||||
*mob_target = None;
|
||||
|
||||
self.target = None;
|
||||
})
|
||||
}
|
||||
|
||||
@ -37,8 +37,7 @@ impl Goal for ZombieAttackGoal {
|
||||
fn stop<'a>(&'a mut self, mob: &'a dyn Mob) -> GoalFuture<'a, ()> {
|
||||
Box::pin(async {
|
||||
self.melee_attack_goal.stop(mob).await;
|
||||
// Assuming set_attacking is synchronous
|
||||
mob.get_mob_entity().set_attacking(false);
|
||||
mob.get_mob_entity().set_attacking(false).await;
|
||||
})
|
||||
}
|
||||
|
||||
@ -46,14 +45,12 @@ impl Goal for ZombieAttackGoal {
|
||||
Box::pin(async {
|
||||
self.melee_attack_goal.tick(mob).await;
|
||||
self.ticks += 1;
|
||||
|
||||
// Note: Accessing cooldown/get_max_cooldown is assumed synchronous.
|
||||
if self.ticks >= 5
|
||||
&& self.melee_attack_goal.cooldown < self.melee_attack_goal.get_max_cooldown()
|
||||
&& self.melee_attack_goal.cooldown < self.melee_attack_goal.get_max_cooldown() / 2
|
||||
{
|
||||
mob.get_mob_entity().set_attacking(true);
|
||||
mob.get_mob_entity().set_attacking(true).await;
|
||||
} else {
|
||||
mob.get_mob_entity().set_attacking(false);
|
||||
mob.get_mob_entity().set_attacking(false).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ impl Navigator {
|
||||
|
||||
// Now let's move
|
||||
entity.entity.set_pos(goal.current_progress);
|
||||
entity.entity.send_pos().await;
|
||||
//entity.entity.send_pos().await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
77
pumpkin/src/entity/mob/creeper.rs
Normal file
77
pumpkin/src/entity/mob/creeper.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::sync::{
|
||||
Arc, Weak,
|
||||
atomic::{AtomicI32, Ordering},
|
||||
};
|
||||
|
||||
use pumpkin_data::{entity::EntityType, meta_data_type::MetaDataType, tracked_data::TrackedData};
|
||||
use pumpkin_protocol::java::client::play::Metadata;
|
||||
|
||||
use crate::entity::{
|
||||
Entity, NBTStorage,
|
||||
ai::goal::{
|
||||
active_target::ActiveTargetGoal, creeper_ignite::CreeperIgniteGoal,
|
||||
look_around::LookAroundGoal, look_at_entity::LookAtEntityGoal,
|
||||
},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
|
||||
pub struct CreeperEntity {
|
||||
pub mob_entity: MobEntity,
|
||||
pub fuse_speed: AtomicI32,
|
||||
}
|
||||
|
||||
impl CreeperEntity {
|
||||
pub async fn make(entity: Entity) -> Arc<Self> {
|
||||
let mob_entity = MobEntity::new(entity);
|
||||
let entity = Self {
|
||||
mob_entity,
|
||||
fuse_speed: AtomicI32::new(-1),
|
||||
};
|
||||
let mob_arc = Arc::new(entity);
|
||||
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().await;
|
||||
let mut target_selector = mob_arc.mob_entity.target_selector.lock().await;
|
||||
|
||||
goal_selector.add_goal(2, Box::new(CreeperIgniteGoal::new(mob_arc.clone())));
|
||||
goal_selector.add_goal(
|
||||
6,
|
||||
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0),
|
||||
);
|
||||
goal_selector.add_goal(6, Box::new(LookAroundGoal::default()));
|
||||
|
||||
target_selector.add_goal(
|
||||
1,
|
||||
ActiveTargetGoal::with_default(&mob_arc.mob_entity, &EntityType::PLAYER, true),
|
||||
);
|
||||
};
|
||||
|
||||
mob_arc
|
||||
}
|
||||
|
||||
pub async fn set_fuse_speed(&self, speed: i32) {
|
||||
dbg!(speed);
|
||||
self.fuse_speed.store(speed, Ordering::Relaxed);
|
||||
self.mob_entity
|
||||
.living_entity
|
||||
.entity
|
||||
.send_meta_data(&[Metadata::new(
|
||||
TrackedData::DATA_FUSE_SPEED,
|
||||
MetaDataType::Integer,
|
||||
speed,
|
||||
)])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for CreeperEntity {}
|
||||
|
||||
impl Mob for CreeperEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.mob_entity
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc};
|
||||
|
||||
use pumpkin_data::entity::EntityType;
|
||||
|
||||
use crate::entity::{
|
||||
Entity, NBTStorage,
|
||||
ai::goal::active_target::ActiveTargetGoal,
|
||||
mob::{Mob, MobEntity, zombie::ZombieEntity},
|
||||
};
|
||||
|
||||
@ -11,9 +14,25 @@ pub struct DrownedEntity {
|
||||
|
||||
impl DrownedEntity {
|
||||
pub async fn make(entity: Entity) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
entity: ZombieEntity::make(entity).await,
|
||||
})
|
||||
let entity = ZombieEntity::make(entity).await;
|
||||
let zombie = Self { entity };
|
||||
let mob_arc = Arc::new(zombie);
|
||||
|
||||
{
|
||||
let mut target_selector = mob_arc.entity.mob_entity.target_selector.lock().await;
|
||||
|
||||
// TODO
|
||||
target_selector.add_goal(
|
||||
2,
|
||||
ActiveTargetGoal::with_default(
|
||||
&mob_arc.entity.mob_entity,
|
||||
&EntityType::PLAYER,
|
||||
true,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
mob_arc
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,14 +6,18 @@ 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::AtomicI32;
|
||||
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;
|
||||
@ -27,9 +31,16 @@ pub struct MobEntity {
|
||||
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 {
|
||||
@ -41,6 +52,7 @@ impl MobEntity {
|
||||
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 {
|
||||
@ -57,8 +69,27 @@ impl MobEntity {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_attacking(&self, _attacking: bool) {
|
||||
// TODO: set to data tracker
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ pub mod hunger;
|
||||
pub mod item;
|
||||
pub mod living;
|
||||
pub mod mob;
|
||||
pub mod passive;
|
||||
pub mod player;
|
||||
pub mod projectile;
|
||||
pub mod projectile_deflection;
|
||||
|
||||
1
pumpkin/src/entity/passive/mod.rs
Normal file
1
pumpkin/src/entity/passive/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod wolf;
|
||||
46
pumpkin/src/entity/passive/wolf.rs
Normal file
46
pumpkin/src/entity/passive/wolf.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use pumpkin_data::entity::EntityType;
|
||||
|
||||
use crate::entity::{
|
||||
Entity, NBTStorage,
|
||||
ai::goal::{look_around::LookAroundGoal, look_at_entity::LookAtEntityGoal},
|
||||
mob::{Mob, MobEntity},
|
||||
};
|
||||
|
||||
pub struct WolfEntity {
|
||||
pub mob_entity: MobEntity,
|
||||
}
|
||||
|
||||
impl WolfEntity {
|
||||
pub async fn make(entity: Entity) -> Arc<Self> {
|
||||
let mob_entity = MobEntity::new(entity);
|
||||
let wolf = Self { mob_entity };
|
||||
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().await;
|
||||
|
||||
// TODO
|
||||
goal_selector.add_goal(
|
||||
8,
|
||||
LookAtEntityGoal::with_default(mob_weak, &EntityType::PLAYER, 8.0),
|
||||
);
|
||||
goal_selector.add_goal(8, Box::new(LookAroundGoal::default()));
|
||||
};
|
||||
|
||||
mob_arc
|
||||
}
|
||||
}
|
||||
|
||||
impl NBTStorage for WolfEntity {}
|
||||
|
||||
impl Mob for WolfEntity {
|
||||
fn get_mob_entity(&self) -> &MobEntity {
|
||||
&self.mob_entity
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,8 @@ use crate::{
|
||||
armor_stand::ArmorStandEntity, end_crystal::EndCrystalEntity, painting::PaintingEntity,
|
||||
},
|
||||
living::LivingEntity,
|
||||
mob::{drowned::DrownedEntity, zombie::ZombieEntity},
|
||||
mob::{creeper::CreeperEntity, drowned::DrownedEntity, zombie::ZombieEntity},
|
||||
passive::wolf::WolfEntity,
|
||||
},
|
||||
world::World,
|
||||
};
|
||||
@ -27,6 +28,8 @@ pub async fn from_type(
|
||||
let mob: Arc<dyn EntityBase> = match entity_type.id {
|
||||
id if id == EntityType::ZOMBIE.id => ZombieEntity::make(entity).await,
|
||||
id if id == EntityType::DROWNED.id => DrownedEntity::make(entity).await,
|
||||
id if id == EntityType::CREEPER.id => CreeperEntity::make(entity).await,
|
||||
id if id == EntityType::WOLF.id => WolfEntity::make(entity).await,
|
||||
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)),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user