Pumpkin/pumpkin/src/block/entities/mob_spawner.rs
2026-05-14 12:28:43 +02:00

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
}
}