From 4aaaea7cc77fd061b5ce40849d2a65a7db9cf104 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 8 Mar 2026 19:41:37 +0100 Subject: [PATCH] feat: Spawners now work for generated Structures --- .../src/block/entities/mob_spawner.rs | 42 ++++++++++++++----- .../src/chunk_system/generation_cache.rs | 24 +++++++++++ .../feature/features/monster_room.rs | 13 ++++-- pumpkin-world/src/generation/proto_chunk.rs | 3 +- .../nether_fortress/bridge_platform.rs | 15 ++++++- .../structures/stronghold/portal_room.rs | 12 ++++-- .../src/generation/structure/template/mod.rs | 4 +- pumpkin/src/block/blocks/spawner.rs | 2 +- 8 files changed, 93 insertions(+), 22 deletions(-) diff --git a/pumpkin-world/src/block/entities/mob_spawner.rs b/pumpkin-world/src/block/entities/mob_spawner.rs index 2ace1dc41..e8d814d47 100644 --- a/pumpkin-world/src/block/entities/mob_spawner.rs +++ b/pumpkin-world/src/block/entities/mob_spawner.rs @@ -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 + 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); }) } diff --git a/pumpkin-world/src/chunk_system/generation_cache.rs b/pumpkin-world/src/chunk_system/generation_cache.rs index eb4af5e25..8e9fb2a50 100644 --- a/pumpkin-world/src/chunk_system/generation_cache.rs +++ b/pumpkin-world/src/chunk_system/generation_cache.rs @@ -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, 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), diff --git a/pumpkin-world/src/generation/feature/features/monster_room.rs b/pumpkin-world/src/generation/feature/features/monster_room.rs index fad9f400e..4c088866a 100644 --- a/pumpkin-world/src/generation/feature/features/monster_room.rs +++ b/pumpkin-world/src/generation/feature/features/monster_room.rs @@ -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 } diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index ca277630b..15b05d856 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -67,6 +67,7 @@ pub trait GenerationCache: HeightLimitView + BlockAccessor { fn get_block_state(&self, pos: &Vector3) -> RawBlockState; fn get_fluid_and_fluid_state(&self, position: &Vector3) -> (Fluid, FluidState); fn set_block_state(&mut self, pos: &Vector3, block_state: &BlockState); + fn add_block_entity(&mut self, pos: &Vector3, 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); } diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_platform.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_platform.rs index cd75149ab..14e474b01 100644 --- a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_platform.rs +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_platform.rs @@ -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); } } diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs b/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs index a33036d21..e5084b81c 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs @@ -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); } } } diff --git a/pumpkin-world/src/generation/structure/template/mod.rs b/pumpkin-world/src/generation/structure/template/mod.rs index 239dd3d8e..4b4ffa6ec 100644 --- a/pumpkin-world/src/generation/structure/template/mod.rs +++ b/pumpkin-world/src/generation/structure/template/mod.rs @@ -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); } } } diff --git a/pumpkin/src/block/blocks/spawner.rs b/pumpkin/src/block/blocks/spawner.rs index 81b3804d6..0b682dc38 100644 --- a/pumpkin/src/block/blocks/spawner.rs +++ b/pumpkin/src/block/blocks/spawner.rs @@ -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;