220 lines
7.3 KiB
Rust
220 lines
7.3 KiB
Rust
use std::{
|
|
pin::Pin,
|
|
sync::{
|
|
Arc,
|
|
atomic::{AtomicI32, Ordering},
|
|
},
|
|
};
|
|
|
|
use crossbeam::atomic::AtomicCell;
|
|
use pumpkin_data::{entity::EntityType, world::WorldEvent};
|
|
use pumpkin_nbt::compound::NbtCompound;
|
|
use pumpkin_util::math::{
|
|
boundingbox::{BoundingBox, EntityDimensions},
|
|
position::BlockPos,
|
|
vector3::Vector3,
|
|
};
|
|
|
|
use crate::{block::entities::BlockEntity, world::World};
|
|
|
|
pub struct MobSpawnerBlockEntity {
|
|
pub position: BlockPos,
|
|
pub delay: AtomicI32,
|
|
pub max_delay: i32,
|
|
pub min_delay: i32,
|
|
pub spawn_count: i32,
|
|
pub spawn_range: i32,
|
|
pub entity_type: AtomicCell<Option<&'static EntityType>>,
|
|
}
|
|
|
|
impl MobSpawnerBlockEntity {
|
|
pub const ID: &'static str = "minecraft:mob_spawner";
|
|
pub const DEFAULT_DELAY: i32 = 20;
|
|
pub const DEFAULT_MAX_SPAWN_DELAY: i32 = 800;
|
|
pub const DEFAULT_MIN_SPAWN_DELAY: i32 = 200;
|
|
pub const DEFAULT_SPAWN_COUNT: i32 = 4;
|
|
pub const DEFAULT_SPAWN_RANGE: i32 = 4;
|
|
|
|
#[must_use]
|
|
pub const fn new(position: BlockPos, entity_type: Option<&'static EntityType>) -> Self {
|
|
Self {
|
|
position,
|
|
delay: AtomicI32::new(Self::DEFAULT_DELAY),
|
|
max_delay: Self::DEFAULT_MAX_SPAWN_DELAY,
|
|
min_delay: Self::DEFAULT_MIN_SPAWN_DELAY,
|
|
spawn_count: Self::DEFAULT_SPAWN_COUNT,
|
|
spawn_range: Self::DEFAULT_SPAWN_RANGE,
|
|
entity_type: AtomicCell::new(entity_type),
|
|
}
|
|
}
|
|
|
|
pub fn write_nbt<'a>(&'a self, nbt: &'a mut NbtCompound) {
|
|
// TODO: this is ugly af
|
|
nbt.put_string("id", self.resource_location().to_string());
|
|
let position = self.get_position();
|
|
nbt.put_int("x", position.0.x);
|
|
nbt.put_int("y", position.0.y);
|
|
nbt.put_int("z", position.0.z);
|
|
if let Some(entity_type) = self.entity_type.load() {
|
|
let mut spawn_entry = NbtCompound::new();
|
|
|
|
let mut entity_nbt = NbtCompound::new();
|
|
entity_nbt.put_string("id", format!("minecraft:{}", entity_type.resource_name));
|
|
|
|
spawn_entry.put_compound("entity", entity_nbt);
|
|
|
|
nbt.put_compound("SpawnData", spawn_entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MobSpawnerBlockEntity {
|
|
async fn update_spawns(&self, world: &Arc<World>) {
|
|
let min_delay = self.min_delay;
|
|
let max_delay = self.max_delay;
|
|
|
|
self.delay.store(
|
|
if max_delay <= min_delay {
|
|
min_delay
|
|
} else {
|
|
min_delay + rand::random_range(0..max_delay - min_delay)
|
|
},
|
|
Ordering::Relaxed,
|
|
);
|
|
world.add_synced_block_event(self.position, 1, 0).await;
|
|
}
|
|
|
|
pub fn set_entity_type(&self, entity_type: &'static EntityType) {
|
|
self.entity_type.store(Some(entity_type));
|
|
}
|
|
}
|
|
|
|
impl BlockEntity for MobSpawnerBlockEntity {
|
|
fn resource_location(&self) -> &'static str {
|
|
Self::ID
|
|
}
|
|
|
|
fn get_position(&self) -> BlockPos {
|
|
self.position
|
|
}
|
|
|
|
fn tick<'a>(&'a self, world: &'a Arc<World>) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
|
Box::pin(async move {
|
|
if let Some(entity_type) = &self.entity_type.load() {
|
|
if self.delay.load(Ordering::Relaxed) == -1 {
|
|
self.update_spawns(world).await;
|
|
} else {
|
|
self.delay.fetch_sub(1, Ordering::Relaxed);
|
|
return;
|
|
}
|
|
let spawn_range = self.spawn_range;
|
|
let mut update_spawns = false;
|
|
for _ in 0..self.spawn_count {
|
|
let pos = self.position.0;
|
|
|
|
let spawn_pos = Vector3::new(
|
|
pos.x as f64
|
|
+ (rand::random::<f64>() + rand::random::<f64>()) * spawn_range as f64
|
|
+ 0.5,
|
|
(pos.y + rand::random_range(0..3) - 1) as f64,
|
|
pos.z as f64
|
|
+ (rand::random::<f64>() + rand::random::<f64>()) * spawn_range as f64
|
|
+ 0.5,
|
|
);
|
|
// TODO: we should use getSpawnBox, but this is only modified for slimes and magma slimes
|
|
if !world.is_space_empty(BoundingBox::new_from_pos(
|
|
spawn_pos.x,
|
|
spawn_pos.y,
|
|
spawn_pos.z,
|
|
&EntityDimensions {
|
|
width: entity_type.dimension[0],
|
|
height: entity_type.dimension[1],
|
|
eye_height: entity_type.eye_height,
|
|
},
|
|
)) {
|
|
continue;
|
|
}
|
|
let entity = crate::entity::r#type::from_type(
|
|
entity_type,
|
|
spawn_pos,
|
|
world,
|
|
uuid::Uuid::new_v4(),
|
|
);
|
|
world.spawn_entity(entity).await;
|
|
world.sync_world_event(WorldEvent::ParticlesMobblockSpawn, self.position, 0);
|
|
update_spawns = true;
|
|
}
|
|
if update_spawns {
|
|
self.update_spawns(world).await;
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn from_nbt(nbt: &pumpkin_nbt::compound::NbtCompound, position: BlockPos) -> Self
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let delay = nbt.get_short("Delay").unwrap_or(Self::DEFAULT_DELAY as i16) as i32;
|
|
let min_delay = nbt
|
|
.get_int("MinSpawnDelay")
|
|
.unwrap_or(Self::DEFAULT_MIN_SPAWN_DELAY);
|
|
let max_delay = nbt
|
|
.get_int("MaxSpawnDelay")
|
|
.unwrap_or(Self::DEFAULT_MAX_SPAWN_DELAY);
|
|
let spawn_count = nbt
|
|
.get_int("SpawnCount")
|
|
.unwrap_or(Self::DEFAULT_SPAWN_COUNT);
|
|
let spawn_range = nbt
|
|
.get_int("SpawnRange")
|
|
.unwrap_or(Self::DEFAULT_SPAWN_RANGE);
|
|
|
|
let entity_type = nbt
|
|
.get_compound("SpawnData")
|
|
.and_then(|data| data.get_compound("entity"))
|
|
.and_then(|entity| entity.get_string("id"))
|
|
.and_then(|id| {
|
|
let name = id.strip_prefix("minecraft:").unwrap_or(id);
|
|
EntityType::from_name(name)
|
|
});
|
|
|
|
Self {
|
|
position,
|
|
delay: AtomicI32::new(delay),
|
|
max_delay,
|
|
min_delay,
|
|
spawn_count,
|
|
spawn_range,
|
|
entity_type: AtomicCell::new(entity_type),
|
|
}
|
|
}
|
|
|
|
fn write_nbt<'a>(
|
|
&'a self,
|
|
nbt: &'a mut NbtCompound,
|
|
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
|
Box::pin(async move {
|
|
self.write_nbt(nbt);
|
|
})
|
|
}
|
|
|
|
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
|
let mut final_nbt = NbtCompound::new();
|
|
if let Some(entity_type) = self.entity_type.load() {
|
|
let mut spawn_entry = NbtCompound::new();
|
|
|
|
let mut entity_nbt = NbtCompound::new();
|
|
entity_nbt.put_string("id", format!("minecraft:{}", entity_type.resource_name));
|
|
|
|
spawn_entry.put_compound("entity", entity_nbt);
|
|
|
|
final_nbt.put_compound("SpawnData", spawn_entry);
|
|
}
|
|
Some(final_nbt)
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn std::any::Any {
|
|
self
|
|
}
|
|
}
|