feat: Spawners now work for generated Structures

This commit is contained in:
Alexander Medvedev
2026-03-08 19:41:37 +01:00
parent e312c4cbd9
commit 4aaaea7cc7
8 changed files with 93 additions and 22 deletions

View File

@@ -36,7 +36,7 @@ impl MobSpawnerBlockEntity {
pub const DEFAULT_SPAWN_RANGE: i32 = 4;
#[must_use]
pub const fn new(position: BlockPos) -> Self {
pub const fn new(position: BlockPos, entity_type: Option<&'static EntityType>) -> Self {
Self {
position,
delay: AtomicI32::new(Self::DEFAULT_DELAY),
@@ -44,7 +44,26 @@ impl MobSpawnerBlockEntity {
min_delay: Self::DEFAULT_MIN_SPAWN_DELAY,
spawn_count: Self::DEFAULT_SPAWN_COUNT,
spawn_range: Self::DEFAULT_SPAWN_RANGE,
entity_type: AtomicCell::new(None),
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);
}
}
}
@@ -151,7 +170,15 @@ impl BlockEntity for MobSpawnerBlockEntity {
let spawn_range = nbt
.get_int("SpawnRange")
.unwrap_or(Self::DEFAULT_SPAWN_RANGE);
let _spawn_entry = nbt.get_compound("SpawnData");
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,
@@ -160,7 +187,7 @@ impl BlockEntity for MobSpawnerBlockEntity {
min_delay,
spawn_count,
spawn_range,
entity_type: AtomicCell::new(None), // TODO
entity_type: AtomicCell::new(entity_type),
}
}
@@ -169,12 +196,7 @@ impl BlockEntity for MobSpawnerBlockEntity {
nbt: &'a mut NbtCompound,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async move {
if let Some(entity_type) = self.entity_type.load() {
let mut entity_nbt = NbtCompound::new();
entity_nbt.put_string("id", format!("minecraft:{}", entity_type.resource_name));
nbt.put_compound("entity", entity_nbt);
}
self.write_nbt(nbt);
})
}

View File

@@ -12,6 +12,7 @@ use pumpkin_data::chunk_gen_settings::GenerationSettings;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::{Block, BlockState};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::HeightMap;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
@@ -205,6 +206,29 @@ impl GenerationCache for Cache {
}
}
fn add_block_entity(&mut self, pos: &Vector3<i32>, nbt: NbtCompound) {
let dx = (pos.x >> 4) - self.x;
let dz = (pos.z >> 4) - self.z;
// debug_assert!(dx < self.size && dz < self.size);
// debug_assert!(dx >= 0 && dz >= 0);
if !(dx < self.size && dz < self.size && dx >= 0 && dz >= 0) {
// breakpoint here
debug!(
"illegal set_block_state {pos:?} cache pos ({}, {}) size {}",
self.x, self.z, self.size
);
return;
}
match &mut self.chunks[(dx * self.size + dz) as usize] {
Chunk::Level(_data) => {
todo!()
}
Chunk::Proto(data) => {
data.add_block_entity(nbt);
}
}
}
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
match heightmap {
HeightMap::WorldSurfaceWg => self.top_block_height_exclusive(x, z),

View File

@@ -1,10 +1,13 @@
use pumpkin_data::{Block, BlockDirection};
use pumpkin_data::{Block, BlockDirection, entity::EntityType};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
math::{position::BlockPos, vector3::Vector3},
random::{RandomGenerator, RandomImpl},
};
use crate::generation::proto_chunk::GenerationCache;
use crate::{
block::entities::mob_spawner::MobSpawnerBlockEntity, generation::proto_chunk::GenerationCache,
};
/// The three mob types that can appear in a dungeon spawner.
///
@@ -143,8 +146,12 @@ impl DungeonFeature {
}
// TODO: set spawner entity type
let _ = DUNGEON_MOBS[random.next_bounded_i32(DUNGEON_MOBS.len() as i32) as usize];
let mob = DUNGEON_MOBS[random.next_bounded_i32(DUNGEON_MOBS.len() as i32) as usize];
chunk.set_block_state(&pos.0, Block::SPAWNER.default_state);
let spawner_block_entity = MobSpawnerBlockEntity::new(pos, EntityType::from_name(mob));
let mut entity_nbt = NbtCompound::new();
spawner_block_entity.write_nbt(&mut entity_nbt);
chunk.add_block_entity(&pos.0, entity_nbt);
true
}

View File

@@ -67,6 +67,7 @@ pub trait GenerationCache: HeightLimitView + BlockAccessor {
fn get_block_state(&self, pos: &Vector3<i32>) -> RawBlockState;
fn get_fluid_and_fluid_state(&self, position: &Vector3<i32>) -> (Fluid, FluidState);
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState);
fn add_block_entity(&mut self, pos: &Vector3<i32>, nbt: NbtCompound);
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32;
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32;
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32;
@@ -350,7 +351,7 @@ impl ProtoChunk {
/// Adds a pending block entity to be created when the chunk is finalized.
///
/// The NBT compound should include the block entity's position (x, y, z) and id fields.
pub fn add_pending_block_entity(&mut self, nbt: NbtCompound) {
pub fn add_block_entity(&mut self, nbt: NbtCompound) {
self.pending_block_entities.push(nbt);
}

View File

@@ -1,11 +1,18 @@
use pumpkin_data::{
Block,
block_properties::{BlockProperties, OakFenceLikeProperties},
entity::EntityType,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
BlockDirection,
math::{block_box::BlockBox, position::BlockPos},
random::RandomGenerator,
};
use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator};
use crate::{
ProtoChunk,
block::entities::mob_spawner::MobSpawnerBlockEntity,
generation::structure::{
piece::StructurePieceType,
structures::{
@@ -140,7 +147,11 @@ impl StructurePieceBase for BridgePlatformPiece {
spawner_pos.z,
Block::SPAWNER.default_state,
);
// TODO: set spawner entity type to Blaze via block entity data
let spawner_block_entity =
MobSpawnerBlockEntity::new(BlockPos(spawner_pos), Some(&EntityType::BLAZE));
let mut entity_nbt = NbtCompound::new();
spawner_block_entity.write_nbt(&mut entity_nbt);
chunk.add_block_entity(entity_nbt);
}
}

View File

@@ -4,15 +4,18 @@ use pumpkin_data::{
BlockProperties, EndPortalFrameLikeProperties, HorizontalFacing, OakFenceLikeProperties,
OakStairsLikeProperties,
},
entity::EntityType,
};
use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::{
BlockDirection,
math::block_box::BlockBox,
math::{block_box::BlockBox, position::BlockPos},
random::{RandomGenerator, RandomImpl},
};
use crate::{
ProtoChunk,
block::entities::mob_spawner::MobSpawnerBlockEntity,
generation::structure::{
piece::StructurePieceType,
structures::{
@@ -355,8 +358,11 @@ impl StructurePieceBase for PortalRoomPiece {
self.spawner_placed = true;
let spawner = Block::SPAWNER.default_state;
inner.add_block(chunk, spawner, 5, 3, 6, &box_limit);
// Note: You must check your engine's API to set the BlockEntity (Silverfish).
// Example: chunk.set_block_entity(pos, BlockEntity::Spawner(EntityType::Silverfish));
let spawner_block_entity =
MobSpawnerBlockEntity::new(BlockPos(pos), Some(&EntityType::SILVERFISH));
let mut entity_nbt = NbtCompound::new();
spawner_block_entity.write_nbt(&mut entity_nbt);
chunk.add_block_entity(entity_nbt);
}
}
}

View File

@@ -96,10 +96,10 @@ pub fn place_template(
let block_entity_id = block_entity_id.unwrap_or(&palette_entry.name);
let mut block_entity_nbt = NbtCompound::new();
block_entity_nbt.put_string("id", block_entity_id.to_string());
block_entity_nbt.put_int("x", wx);
block_entity_nbt.put_int("y", wy);
block_entity_nbt.put_int("z", wz);
block_entity_nbt.put_string("id", block_entity_id.to_string());
if let Some(template_nbt) = &block.nbt {
for (key, value) in &template_nbt.child_tags {
@@ -111,7 +111,7 @@ pub fn place_template(
}
}
chunk.add_pending_block_entity(block_entity_nbt);
chunk.add_block_entity(block_entity_nbt);
}
}
}

View File

@@ -11,7 +11,7 @@ pub struct SpawnerBlock;
impl BlockBehaviour for SpawnerBlock {
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
Box::pin(async move {
let hopper_block_entity = MobSpawnerBlockEntity::new(*args.position);
let hopper_block_entity = MobSpawnerBlockEntity::new(*args.position, None);
args.world
.add_block_entity(Arc::new(hopper_block_entity))
.await;