From 3f4cdd0695d1871e5242a8873e522d0c5628765f Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 6 Mar 2026 09:42:54 +0100 Subject: [PATCH] feat: Nether Fortress Co-Authored-By: Richard Marshall <29306641+Purdze@users.noreply.github.com> --- pumpkin-util/src/lib.rs | 11 +- .../src/chunk_system/generation_cache.rs | 10 +- pumpkin-world/src/generation/proto_chunk.rs | 41 +- pumpkin-world/src/generation/structure/mod.rs | 7 +- .../structure/structures/buried_treasure.rs | 8 +- .../generation/structure/structures/igloo.rs | 13 +- .../generation/structure/structures/mod.rs | 48 +- .../structure/structures/nether_fortress.rs | 188 ------ .../structures/nether_fortress/bridge.rs | 155 +++++ .../nether_fortress/bridge_crossing.rs | 189 ++++++ .../structures/nether_fortress/bridge_end.rs | 120 ++++ .../nether_fortress/bridge_platform.rs | 157 +++++ .../nether_fortress/bridge_small_crossing.rs | 163 +++++ .../nether_fortress/bridge_stairs.rs | 149 +++++ .../nether_fortress/corridor_balcony.rs | 171 +++++ .../nether_fortress/corridor_crossing.rs | 137 ++++ .../nether_fortress/corridor_exit.rs | 236 +++++++ .../nether_fortress/corridor_left_turn.rs | 154 +++++ .../corridor_nether_warts_room.rs | 281 ++++++++ .../nether_fortress/corridor_right_turn.rs | 154 +++++ .../nether_fortress/corridor_stairs.rs | 152 +++++ .../structures/nether_fortress/mod.rs | 624 ++++++++++++++++++ .../nether_fortress/small_corridor.rs | 129 ++++ .../structure/structures/nether_fossil.rs | 10 +- .../structures/stronghold/chest_corridor.rs | 10 +- .../structures/stronghold/corridor.rs | 20 +- .../stronghold/five_way_crossing.rs | 10 +- .../structures/stronghold/left_turn.rs | 10 +- .../structures/stronghold/library.rs | 10 +- .../structures/stronghold/portal_room.rs | 10 +- .../structures/stronghold/prison_hall.rs | 10 +- .../structures/stronghold/right_turn.rs | 10 +- .../structures/stronghold/spiral_staircase.rs | 12 +- .../structures/stronghold/square_room.rs | 10 +- .../structure/structures/stronghold/stairs.rs | 10 +- .../structure/structures/swamp_hut.rs | 20 +- .../structure/template/template_piece.rs | 8 +- 37 files changed, 3181 insertions(+), 276 deletions(-) delete mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/bridge.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_crossing.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_end.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_platform.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_small_crossing.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_stairs.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_balcony.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_crossing.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_left_turn.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_nether_warts_room.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_right_turn.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_stairs.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs create mode 100644 pumpkin-world/src/generation/structure/structures/nether_fortress/small_corridor.rs diff --git a/pumpkin-util/src/lib.rs b/pumpkin-util/src/lib.rs index 54d96840a..b7b9e2388 100644 --- a/pumpkin-util/src/lib.rs +++ b/pumpkin-util/src/lib.rs @@ -7,7 +7,7 @@ pub use difficulty::Difficulty; pub use gamemode::GameMode; pub use permission::PermissionLvl; -use crate::math::vector3::Axis; +use crate::{math::vector3::Axis, random::RandomImpl}; pub mod biome; pub mod difficulty; @@ -131,6 +131,15 @@ impl BlockDirection { Self::East | Self::West => Axis::X, } } + + pub fn get_random_horizontal_direction(random: &mut impl RandomImpl) -> Self { + match random.next_bounded_i32(4) { + 0 => Self::North, + 1 => Self::East, + 2 => Self::South, + _ => Self::West, + } + } } /// A mutable slice split into three parts: the element at the split index, the start, and the end. diff --git a/pumpkin-world/src/chunk_system/generation_cache.rs b/pumpkin-world/src/chunk_system/generation_cache.rs index 0e1115405..eb4af5e25 100644 --- a/pumpkin-world/src/chunk_system/generation_cache.rs +++ b/pumpkin-world/src/chunk_system/generation_cache.rs @@ -90,8 +90,14 @@ impl GenerationCache for Cache { let dx = chunk_x - self.x; let dz = chunk_z - self.z; - (dx >= 0 && dx < self.size && dz >= 0 && dz < self.size) - .then(|| self.chunks[(dx * self.size + dz) as usize].get_proto_chunk()) + if dx < 0 || dx >= self.size || dz < 0 || dz >= self.size { + return None; + } + + match &self.chunks[(dx * self.size + dz) as usize] { + Chunk::Proto(chunk) => Some(chunk), + Chunk::Level(_) => None, + } } fn try_get_proto_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ProtoChunk> { diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index f34a1b222..5272645ce 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -1022,14 +1022,10 @@ impl ProtoChunk { match instance { StructureInstance::Start(pos) => tasks.push(pos.collector.clone()), - StructureInstance::Reference(origin_block_pos) => { - let origin_chunk_x = origin_block_pos.0.x >> 4; - let origin_chunk_z = origin_block_pos.0.z >> 4; - if let Some(neighbor) = cache.get_chunk(origin_chunk_x, origin_chunk_z) - && let Some(StructureInstance::Start(pos)) = - neighbor.structure_starts.get(id) - { - tasks.push(pos.collector.clone()); + StructureInstance::Reference(collector) => { + let collector_arc = collector.clone(); + if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) { + tasks.push(collector_arc); } } } @@ -1069,29 +1065,10 @@ impl ProtoChunk { } } } - StructureInstance::Reference(origin_block_pos) => { - let origin_chunk_x = origin_block_pos.0.x >> 4; - let origin_chunk_z = origin_block_pos.0.z >> 4; - if let Some(origin_neighbor) = - cache.try_get_proto_chunk(origin_chunk_x, origin_chunk_z) - && let Some(StructureInstance::Start(pos)) = - origin_neighbor.structure_starts.get(id) - { - let start_x = chunk_pos::start_block_x(center_x); - let start_z = chunk_pos::start_block_z(center_z); - let end_x = start_x + 15; - let end_z = start_z + 15; - - if pos - .get_bounding_box() - .intersects_raw_xz(start_x, start_z, end_x, end_z) - { - let collector_arc = pos.collector.clone(); - if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) - { - tasks.push(collector_arc); - } - } + StructureInstance::Reference(collector) => { + let collector_arc = collector.clone(); + if !tasks.iter().any(|t| Arc::ptr_eq(t, &collector_arc)) { + tasks.push(collector_arc); } } } @@ -1218,7 +1195,7 @@ impl ProtoChunk { .get_bounding_box() .intersects_raw_xz(start_x, start_z, end_x, end_z) { - references.push((*key, start_data.start_pos)); + references.push((*key, start_data.collector.clone())); } } } diff --git a/pumpkin-world/src/generation/structure/mod.rs b/pumpkin-world/src/generation/structure/mod.rs index ae5ae895f..e8d83529d 100644 --- a/pumpkin-world/src/generation/structure/mod.rs +++ b/pumpkin-world/src/generation/structure/mod.rs @@ -10,8 +10,8 @@ use crate::{ structure::structures::{ StructureGenerator, StructureGeneratorContext, StructurePosition, buried_treasure::BuriedTreasureGenerator, create_chunk_random, igloo::IglooGenerator, - nether_fossil::NetherFossilGenerator, stronghold::StrongholdGenerator, - swamp_hut::SwampHutGenerator, + nether_fortress::NetherFortressGenerator, nether_fossil::NetherFossilGenerator, + stronghold::StrongholdGenerator, swamp_hut::SwampHutGenerator, }, }, }; @@ -49,6 +49,9 @@ pub fn try_generate_structure( StructureKeys::Stronghold => { StrongholdGenerator::get_structure_position(&StrongholdGenerator, context) } + StructureKeys::Fortress => { + NetherFortressGenerator::get_structure_position(&NetherFortressGenerator, context) + } StructureKeys::NetherFossil => { NetherFossilGenerator::get_structure_position(&NetherFossilGenerator, context) } diff --git a/pumpkin-world/src/generation/structure/structures/buried_treasure.rs b/pumpkin-world/src/generation/structure/structures/buried_treasure.rs index bc855bac0..96d131c71 100644 --- a/pumpkin-world/src/generation/structure/structures/buried_treasure.rs +++ b/pumpkin-world/src/generation/structure/structures/buried_treasure.rs @@ -56,7 +56,13 @@ impl StructurePieceBase for BuriedTreasurePiece { Box::new((*self).clone()) } - fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + _chunk_box: &BlockBox, + ) { let boundingbox = self.bounding_box(); let y = chunk.get_top_y( &HeightMap::OceanFloorWg, diff --git a/pumpkin-world/src/generation/structure/structures/igloo.rs b/pumpkin-world/src/generation/structure/structures/igloo.rs index 17a7d9cf8..7d48afedb 100644 --- a/pumpkin-world/src/generation/structure/structures/igloo.rs +++ b/pumpkin-world/src/generation/structure/structures/igloo.rs @@ -8,7 +8,10 @@ use std::sync::Arc; -use pumpkin_util::{math::position::BlockPos, math::vector3::Vector3, random::RandomGenerator}; +use pumpkin_util::{ + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, + random::RandomGenerator, +}; use serde::Deserialize; use crate::{ @@ -142,7 +145,13 @@ impl StructurePieceBase for IglooPiece { Box::new(self.clone()) } - fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + _chunk_box: &BlockBox, + ) { let origin = self.shiftable_structure_piece.piece.bounding_box.min; // Vanilla samples height at the entrance position (3, 0, 5 in template space) diff --git a/pumpkin-world/src/generation/structure/structures/mod.rs b/pumpkin-world/src/generation/structure/structures/mod.rs index a0ac00814..1443a3c3a 100644 --- a/pumpkin-world/src/generation/structure/structures/mod.rs +++ b/pumpkin-world/src/generation/structure/structures/mod.rs @@ -10,7 +10,6 @@ use pumpkin_util::{ }; use tracing::debug; -use crate::generation::structure::structures::stronghold::PieceWeight; use crate::generation::structure::structures::stronghold::StrongholdPieceType; use crate::{ ProtoChunk, @@ -49,7 +48,13 @@ pub trait StructurePieceBase: Send + Sync { fn clone_box(&self) -> Box; /// Places the blocks for this piece into the chunk. - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, seed: i64); + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + seed: i64, + _chunk_box: &BlockBox, + ); #[expect(clippy::too_many_arguments)] fn fill_openings( @@ -57,7 +62,7 @@ pub trait StructurePieceBase: Send + Sync { _start: &StructurePiece, _random: &mut RandomGenerator, // TODO: this is only for Stronghold and should not be here - _weights: &mut Vec, + _weights: &mut Vec, _last_piece_type: &mut Option, _has_portal_room: &mut bool, @@ -65,6 +70,21 @@ pub trait StructurePieceBase: Send + Sync { _pieces_to_process: &mut Vec>, ) { } + + fn fill_openings_nether( + &self, + _start: &StructurePiece, + _random: &mut RandomGenerator, + _bridge_pieces: &mut Vec< + crate::generation::structure::structures::nether_fortress::PieceWeight, + >, + _corridor_pieces: &mut Vec< + crate::generation::structure::structures::nether_fortress::PieceWeight, + >, + _collector: &mut StructurePiecesCollector, + _pieces_to_process: &mut Vec>, + ) { + } } #[derive(Clone)] @@ -366,15 +386,6 @@ impl StructurePiece { // world.mark_block_for_post_processing(&block_pos); // } } - - pub fn get_random_horizontal_direction(random: &mut impl RandomImpl) -> BlockDirection { - match random.next_bounded_i32(4) { - 0 => BlockDirection::North, - 1 => BlockDirection::East, - 2 => BlockDirection::South, - _ => BlockDirection::West, - } - } } impl StructurePieceBase for StructurePiece { @@ -382,7 +393,14 @@ impl StructurePieceBase for StructurePiece { Box::new(self.clone()) } - fn place(&mut self, _chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) {} + fn place( + &mut self, + _chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + _chunk_box: &BlockBox, + ) { + } fn translate(&mut self, x: i32, y: i32, z: i32) { self.bounding_box.move_pos(x, y, z); @@ -451,7 +469,7 @@ impl StructurePiecesCollector { for piece in &mut self.pieces { if piece.bounding_box().intersects(&chunk_box) { - piece.place(chunk, random, seed); + piece.place(chunk, random, seed, &chunk_box); } } } @@ -552,5 +570,5 @@ pub enum StructureInstance { Start(StructurePosition), /// This chunk just contains a piece of a structure starting elsewhere. /// Stores the `BlockPos` of the 'Start' so you can look it up. - Reference(BlockPos), + Reference(Arc>), } diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress.rs deleted file mode 100644 index 745f5e5e6..000000000 --- a/pumpkin-world/src/generation/structure/structures/nether_fortress.rs +++ /dev/null @@ -1,188 +0,0 @@ -// use std::sync::Arc; - -// use pumpkin_data::Block; -// use pumpkin_util::{ -// math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, -// random::RandomGenerator, -// }; -// use serde::Deserialize; - -// use crate::{ -// ProtoChunk, -// generation::{ -// positions::chunk_pos, -// structure::structures::{ -// StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, -// StructurePiecesCollector, StructurePosition, -// }, -// }, -// }; - -// #[derive(Deserialize, Clone, Debug)] -// pub struct NetherFortressGenerator; - -// impl StructureGenerator for NetherFortressGenerator { -// fn get_structure_position( -// &self, -// context: StructureGeneratorContext, -// ) -> Option { -// let start_x = chunk_pos::start_block_x(context.chunk_x); -// let start_z = chunk_pos::start_block_z(context.chunk_z); -// let start_y = 64; - -// let mut collector = StructurePiecesCollector::default(); - -// // Add a Bridge Platform piece -// let box_min = BlockPos::new(start_x, start_y, start_z); -// let box_max = BlockPos::new(start_x + 8, start_y + 10, start_z + 8); - -// todo!(); - -// Some(StructurePosition { -// start_pos: box_min, -// collector: Arc::new(collector.into()), -// }) -// } -// } - -// #[derive(Debug, Clone)] -// pub enum NetherFortressPieceType { -// BridgePlatform, -// SmallCorridor, -// } - -// struct NetherFortressPiece { -// piece: StructurePiece, -// piece_type: NetherFortressPieceType, -// } - -// impl StructurePieceBase for NetherFortressPiece { -// fn place(&mut self, chunk: &mut ProtoChunk, _seed: i64) { -// let bounding_box = self.piece.bounding_box; -// let origin_x = bounding_box.min.x; -// let origin_y = bounding_box.min.y; -// let origin_z = bounding_box.min.z; - -// let offset = |x, y, z| Vector3::new(origin_x + x, origin_y + y, origin_z + z); - -// let fill_rel = |min_x, min_y, min_z, max_x, max_y, max_z, block, c: &mut ProtoChunk| { -// fill( -// origin_x + min_x, -// origin_y + min_y, -// origin_z + min_z, -// origin_x + max_x, -// origin_y + max_y, -// origin_z + max_z, -// block, -// c, -// ) -// }; - -// let fill_outline_rel = -// |min_x, min_y, min_z, max_x, max_y, max_z, outline, inside, c: &mut ProtoChunk| { -// fill_with_outline( -// origin_x + min_x, -// origin_y + min_y, -// origin_z + min_z, -// origin_x + max_x, -// origin_y + max_y, -// origin_z + max_z, -// outline, -// inside, -// c, -// ) -// }; - -// match self.piece_type { -// NetherFortressPieceType::BridgePlatform => { -// fill_rel(0, 2, 0, 6, 7, 7, Block::AIR.default_state, chunk); -// fill_rel(1, 0, 0, 5, 1, 7, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(1, 2, 1, 5, 2, 7, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(1, 3, 2, 5, 3, 7, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(1, 4, 3, 5, 4, 7, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(1, 2, 0, 1, 4, 2, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(5, 2, 0, 5, 4, 2, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(1, 5, 2, 1, 5, 3, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(5, 5, 2, 5, 5, 3, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(0, 5, 3, 0, 5, 8, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(6, 5, 3, 6, 5, 8, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(1, 5, 8, 5, 5, 8, Block::NETHER_BRICKS.default_state, chunk); - -// let fence = &Block::NETHER_BRICK_FENCE.default_state; - -// chunk.set_block_state(&offset(1, 6, 3), fence); -// chunk.set_block_state(&offset(5, 6, 3), fence); -// chunk.set_block_state(&offset(0, 6, 3), fence); -// chunk.set_block_state(&offset(6, 6, 3), fence); - -// fill_outline_rel(0, 6, 4, 0, 6, 7, fence, fence, chunk); -// fill_outline_rel(6, 6, 4, 6, 6, 7, fence, fence, chunk); - -// chunk.set_block_state(&offset(0, 6, 8), fence); -// chunk.set_block_state(&offset(6, 6, 8), fence); - -// fill_outline_rel(1, 6, 8, 5, 6, 8, fence, fence, chunk); - -// chunk.set_block_state(&offset(1, 7, 8), fence); -// fill_outline_rel(2, 7, 8, 4, 7, 8, fence, fence, chunk); -// chunk.set_block_state(&offset(5, 7, 8), fence); - -// chunk.set_block_state(&offset(2, 8, 8), fence); -// chunk.set_block_state(&offset(3, 8, 8), fence); -// chunk.set_block_state(&offset(4, 8, 8), fence); - -// // Blaze Spawner -// chunk.set_block_state(&offset(3, 5, 5), Block::SPAWNER.default_state); - -// // Pillars down -// for x in 0..6 { -// for z in 0..6 { -// fill_downwards( -// origin_x + x, -// origin_y - 1, -// origin_z + z, -// Block::NETHER_BRICKS.default_state, -// chunk, -// ); -// } -// } -// } -// NetherFortressPieceType::SmallCorridor => { -// fill_rel(0, 0, 0, 4, 1, 4, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(0, 2, 0, 4, 5, 4, Block::AIR.default_state, chunk); - -// let fence = &Block::NETHER_BRICK_FENCE.default_state; - -// fill_rel(0, 2, 0, 0, 5, 4, Block::NETHER_BRICKS.default_state, chunk); -// fill_rel(4, 2, 0, 4, 5, 4, Block::NETHER_BRICKS.default_state, chunk); - -// fill_rel(0, 3, 1, 0, 4, 1, fence, chunk); -// fill_rel(0, 3, 3, 0, 4, 3, fence, chunk); -// fill_rel(4, 3, 1, 4, 4, 1, fence, chunk); -// fill_rel(4, 3, 3, 4, 4, 3, fence, chunk); - -// fill_rel(0, 6, 0, 4, 6, 4, Block::NETHER_BRICKS.default_state, chunk); - -// for i in 0..=4 { -// for j in 0..=4 { -// fill_downwards( -// origin_x + i, -// origin_y - 1, -// origin_z + j, -// Block::NETHER_BRICKS.default_state, -// chunk, -// ); -// } -// } -// } -// } -// } - -// fn get_structure_piece(&self) -> &super::StructurePiece { -// &self.piece -// } - -// fn get_structure_piece_mut(&mut self) -> &mut super::StructurePiece { -// &mut self.piece -// } -// } diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge.rs new file mode 100644 index 000000000..3f6b8ea78 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge.rs @@ -0,0 +1,155 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Straight exterior bridge segment (5 × 10 × 19). +#[derive(Clone)] +pub struct BridgePiece { + pub piece: NetherFortressPiece, +} + +impl BridgePiece { + pub fn create( + collector: &mut StructurePiecesCollector, + _random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -1, -3, 0, 5, 10, 19, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = + NetherFortressPiece::new(StructurePieceType::NetherFortressBridge, chain_length, bbox); + piece.piece.set_facing(Some(orientation)); + + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for BridgePiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last_piece: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last_piece, + 1, + 3, + false, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + p.fill_with_outline(chunk, &bb, false, 0, 3, 0, 4, 4, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 5, 0, 3, 7, 18, air, air); + p.fill_with_outline(chunk, &bb, false, 0, 5, 0, 0, 5, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 5, 0, 4, 5, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 4, 2, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 13, 4, 2, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 4, 1, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 0, 15, 4, 1, 18, nb, nb); + + for i in 0..=4i32 { + for j in 0..=2i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + p.fill_downwards(chunk, nb, i, -1, 18 - j, &bb); + } + } + + // North-South fence (connects along X axis on left/right walls) + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.north = true; + props.south = true; + let _fence_ns = + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)); + + // fence_ns + east (left wall) + props.east = true; + props.north = true; + props.south = true; + let fence_east = + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)); + + // fence_ns + west (right wall) + props.east = false; + props.west = true; + let fence_west = + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)); + + p.fill_with_outline(chunk, &bb, false, 0, 1, 1, 0, 4, 1, fence_east, fence_east); + p.fill_with_outline(chunk, &bb, false, 0, 3, 4, 0, 4, 4, fence_east, fence_east); + p.fill_with_outline( + chunk, &bb, false, 0, 3, 14, 0, 4, 14, fence_east, fence_east, + ); + p.fill_with_outline( + chunk, &bb, false, 0, 1, 17, 0, 4, 17, fence_east, fence_east, + ); + p.fill_with_outline(chunk, &bb, false, 4, 1, 1, 4, 4, 1, fence_west, fence_west); + p.fill_with_outline(chunk, &bb, false, 4, 3, 4, 4, 4, 4, fence_west, fence_west); + p.fill_with_outline( + chunk, &bb, false, 4, 3, 14, 4, 4, 14, fence_west, fence_west, + ); + p.fill_with_outline( + chunk, &bb, false, 4, 1, 17, 4, 4, 17, fence_west, fence_west, + ); + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_crossing.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_crossing.rs new file mode 100644 index 000000000..5e0d31d3d --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_crossing.rs @@ -0,0 +1,189 @@ +use pumpkin_data::Block; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// 4-way exterior bridge crossing (19 × 10 × 19). +/// Also serves as the generation start piece. +#[derive(Clone)] +pub struct BridgeCrossingPiece { + pub piece: NetherFortressPiece, +} + +impl BridgeCrossingPiece { + /// Create as the very first (start) piece at a fixed y=64. + pub fn new_start(random: &mut impl RandomImpl, x: i32, z: i32) -> Self { + let orientation = BlockDirection::get_random_horizontal_direction(random); + let bbox = BlockBox::create_box(x, 64, z, orientation.get_axis(), 19, 10, 19); + let mut piece = + NetherFortressPiece::new(StructurePieceType::NetherFortressBridgeCrossing, 0, bbox); + piece.piece.set_facing(Some(orientation)); + Self { piece } + } + + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -8, -3, 0, 19, 10, 19, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressBridgeCrossing, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } + + pub fn fill_openings( + &self, + start: &StructurePiece, + random: &mut impl RandomImpl, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last_piece: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last_piece, + 8, + 3, + false, + pieces_to_process, + ); + self.piece.fill_nw_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last_piece, + 3, + 8, + false, + pieces_to_process, + ); + self.piece.fill_se_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last_piece, + 3, + 8, + false, + pieces_to_process, + ); + } +} + +impl StructurePieceBase for BridgeCrossingPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + self.fill_openings( + start, + random, + bridge_pieces, + corridor_pieces, + collector, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + p.fill_with_outline(chunk, &bb, false, 7, 3, 0, 11, 4, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 3, 7, 18, 4, 11, nb, nb); + p.fill_with_outline(chunk, &bb, false, 8, 5, 0, 10, 7, 18, air, air); + p.fill_with_outline(chunk, &bb, false, 0, 5, 8, 18, 7, 10, air, air); + p.fill_with_outline(chunk, &bb, false, 7, 5, 0, 7, 5, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 7, 5, 11, 7, 5, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 11, 5, 0, 11, 5, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 11, 5, 11, 11, 5, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 5, 7, 7, 5, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 11, 5, 7, 18, 5, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 5, 11, 7, 5, 11, nb, nb); + p.fill_with_outline(chunk, &bb, false, 11, 5, 11, 18, 5, 11, nb, nb); + p.fill_with_outline(chunk, &bb, false, 7, 2, 0, 11, 2, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 7, 2, 13, 11, 2, 18, nb, nb); + p.fill_with_outline(chunk, &bb, false, 7, 0, 0, 11, 1, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 7, 0, 15, 11, 1, 18, nb, nb); + + for i in 7..=11i32 { + for j in 0..=2i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + p.fill_downwards(chunk, nb, i, -1, 18 - j, &bb); + } + } + + p.fill_with_outline(chunk, &bb, false, 0, 2, 7, 5, 2, 11, nb, nb); + p.fill_with_outline(chunk, &bb, false, 13, 2, 7, 18, 2, 11, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 0, 7, 3, 1, 11, nb, nb); + p.fill_with_outline(chunk, &bb, false, 15, 0, 7, 18, 1, 11, nb, nb); + + for i in 0..=2i32 { + for j in 7..=11i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + p.fill_downwards(chunk, nb, i, -1, 18 - j, &bb); // Note: Java uses `18 - i` but iterates j; faithful copy + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_end.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_end.rs new file mode 100644 index 000000000..9919a1b13 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_end.rs @@ -0,0 +1,120 @@ +use pumpkin_data::Block; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, PieceWeight}, + }, + }, +}; + +/// Dead-end / terminator piece for the exterior bridge (5 × 10 × 8). +/// Generated randomly on each side and used as a fallback when no valid piece +/// can be placed. +#[derive(Clone)] +pub struct BridgeEndPiece { + pub piece: NetherFortressPiece, + /// Seed used so the crumbling geometry is deterministic per world seed. + pub seed: u32, +} + +impl BridgeEndPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -1, -3, 0, 5, 10, 8, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let seed = random.next_i32() as u32; + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressBridgeEnd, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece, seed })) + } +} + +impl StructurePieceBase for BridgeEndPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + // BridgeEnd is a terminal piece – it never spawns children. + fn fill_openings_nether( + &self, + _start: &StructurePiece, + _random: &mut RandomGenerator, + _bridge_pieces: &mut Vec, + _corridor_pieces: &mut Vec, + _collector: &mut StructurePiecesCollector, + _pieces_to_process: &mut Vec>, + ) { + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + + // Use a seeded local random so geometry is deterministic. + let mut rng = pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed(self.seed as u64); + + for i in 0..=4i32 { + for j in 3..=4i32 { + let k = rng.next_bounded_i32(8); + p.fill_with_outline(chunk, &bb, false, i, j, 0, i, j, k, nb, nb); + } + } + + let k0 = rng.next_bounded_i32(8); + p.fill_with_outline(chunk, &bb, false, 0, 5, 0, 0, 5, k0, nb, nb); + + let k1 = rng.next_bounded_i32(8); + p.fill_with_outline(chunk, &bb, false, 4, 5, 0, 4, 5, k1, nb, nb); + + for i in 0..=4i32 { + let j = rng.next_bounded_i32(5); + p.fill_with_outline(chunk, &bb, false, i, 2, 0, i, 2, j, nb, nb); + } + + for i in 0..=4i32 { + for j in 0..=1i32 { + let k = rng.next_bounded_i32(3); + p.fill_with_outline(chunk, &bb, false, i, j, 0, i, j, k, nb, nb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} 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 new file mode 100644 index 000000000..cd75149ab --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_platform.rs @@ -0,0 +1,157 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, PieceWeight}, + }, + }, +}; + +/// Exterior platform with a Blaze mob-spawner (7 × 8 × 9). +#[derive(Clone)] +pub struct BridgePlatformPiece { + pub piece: NetherFortressPiece, + pub has_blaze_spawner: bool, +} + +impl BridgePlatformPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + chain_length: u32, + orientation: BlockDirection, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -2, 0, 0, 7, 8, 9, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressBridgePlatform, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { + piece, + has_blaze_spawner: false, + })) + } +} + +impl StructurePieceBase for BridgePlatformPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + // Terminal piece – no children. + fn fill_openings_nether( + &self, + _start: &StructurePiece, + _random: &mut RandomGenerator, + _bridge_pieces: &mut Vec, + _corridor_pieces: &mut Vec, + _collector: &mut StructurePiecesCollector, + _pieces_to_process: &mut Vec>, + ) { + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 6, 7, 7, air, air); + p.fill_with_outline(chunk, &bb, false, 1, 0, 0, 5, 1, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 2, 1, 5, 2, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 3, 2, 5, 3, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 4, 3, 5, 4, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 2, 0, 1, 4, 2, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 2, 0, 5, 4, 2, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 5, 2, 1, 5, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 5, 2, 5, 5, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 5, 3, 0, 5, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 5, 3, 6, 5, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 5, 8, 5, 5, 8, nb, nb); + + // Fence helpers + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + + p.add_block(chunk, fence(true, false, false, false), 1, 6, 3, &bb); + p.add_block(chunk, fence(false, true, false, false), 5, 6, 3, &bb); + p.add_block(chunk, fence(false, true, true, false), 0, 6, 3, &bb); + p.add_block(chunk, fence(true, false, true, false), 6, 6, 3, &bb); + + p.fill_with_outline(chunk, &bb, false, 0, 6, 4, 0, 6, 7, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 6, 6, 4, 6, 6, 7, f_ns, f_ns); + + p.add_block(chunk, fence(false, true, false, true), 0, 6, 8, &bb); + p.add_block(chunk, fence(true, false, false, true), 6, 6, 8, &bb); + p.fill_with_outline(chunk, &bb, false, 1, 6, 8, 5, 6, 8, f_ew, f_ew); + + p.add_block(chunk, fence(false, true, false, false), 1, 7, 8, &bb); + p.fill_with_outline(chunk, &bb, false, 2, 7, 8, 4, 7, 8, f_ew, f_ew); + p.add_block(chunk, fence(true, false, false, false), 5, 7, 8, &bb); + + p.add_block(chunk, fence(false, true, false, false), 2, 8, 8, &bb); + p.add_block(chunk, f_ew, 3, 8, 8, &bb); + p.add_block(chunk, fence(true, false, false, false), 4, 8, 8, &bb); + + // Blaze spawner (placed once) + if !self.has_blaze_spawner { + let spawner_pos = p.offset_pos(3, 5, 5); + if bb.contains_pos(&spawner_pos) { + self.has_blaze_spawner = true; + chunk.set_block_state( + spawner_pos.x, + spawner_pos.y, + spawner_pos.z, + Block::SPAWNER.default_state, + ); + // TODO: set spawner entity type to Blaze via block entity data + } + } + + for i in 0..=6i32 { + for j in 0..=6i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_small_crossing.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_small_crossing.rs new file mode 100644 index 000000000..f671ebbe9 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_small_crossing.rs @@ -0,0 +1,163 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Small 3-way crossing for the exterior bridge network (7 × 9 × 7). +#[derive(Clone)] +pub struct BridgeSmallCrossingPiece { + pub piece: NetherFortressPiece, +} + +impl BridgeSmallCrossingPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -2, 0, 0, 7, 9, 7, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressBridgeSmallCrossing, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for BridgeSmallCrossingPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 2, + 0, + false, + pieces_to_process, + ); + self.piece.fill_nw_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + 2, + false, + pieces_to_process, + ); + self.piece.fill_se_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + 2, + false, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 6, 1, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 6, 7, 6, air, air); + + // Walls + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 1, 6, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 6, 1, 6, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 2, 0, 6, 6, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 2, 6, 6, 6, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 0, 6, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 5, 0, 6, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 2, 0, 6, 6, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 2, 5, 6, 6, 6, nb, nb); + + // Fence helpers + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + + // Battlements: top of the four open sides + p.fill_with_outline(chunk, &bb, false, 2, 6, 0, 4, 6, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 0, 4, 5, 0, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 2, 6, 6, 4, 6, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 6, 4, 5, 6, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 0, 6, 2, 0, 6, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 5, 2, 0, 5, 4, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 6, 6, 2, 6, 6, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 5, 2, 6, 5, 4, f_ns, f_ns); + + for i in 0..=6i32 { + for j in 0..=6i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_stairs.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_stairs.rs new file mode 100644 index 000000000..db486ce70 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/bridge_stairs.rs @@ -0,0 +1,149 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Stairway that transitions from bridge level to interior level (7 × 11 × 7). +#[derive(Clone)] +pub struct BridgeStairsPiece { + pub piece: NetherFortressPiece, +} + +impl BridgeStairsPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + chain_length: u32, + orientation: BlockDirection, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -2, 0, 0, 7, 11, 7, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressBridgeStairs, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for BridgeStairsPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + // SE opening connects into the corridor network (inside = true) + self.piece.fill_se_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 6, + 2, + false, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 6, 1, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 6, 10, 6, air, air); + + // Walls + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 1, 8, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 2, 0, 6, 8, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 1, 0, 8, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 2, 1, 6, 8, 6, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 2, 6, 5, 8, 6, nb, nb); + + // Fence helpers + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + + // Fence railings + p.fill_with_outline(chunk, &bb, false, 0, 3, 2, 0, 5, 4, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 6, 3, 2, 6, 5, 2, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 6, 3, 4, 6, 5, 4, f_ns, f_ns); + + // Stair steps (bottom to top, going from z=5 → z=1) + p.add_block(chunk, nb, 5, 2, 5, &bb); + p.fill_with_outline(chunk, &bb, false, 4, 2, 5, 4, 3, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 3, 2, 5, 3, 4, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 2, 5, 2, 5, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 2, 5, 1, 6, 5, nb, nb); + + // Upper floor platform + p.fill_with_outline(chunk, &bb, false, 1, 7, 1, 5, 7, 4, nb, nb); + + // Open the SE exit gap + p.fill_with_outline(chunk, &bb, false, 6, 8, 2, 6, 8, 4, air, air); + + // Battlement above forward opening + p.fill_with_outline(chunk, &bb, false, 2, 6, 0, 4, 8, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 0, 4, 5, 0, f_ew, f_ew); + + for i in 0..=6i32 { + for j in 0..=6i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_balcony.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_balcony.rs new file mode 100644 index 000000000..d9ab021b1 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_balcony.rs @@ -0,0 +1,171 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Interior corridor piece with a balcony overlook (9 × 7 × 9). +#[derive(Clone)] +pub struct CorridorBalconyPiece { + pub piece: NetherFortressPiece, +} + +impl CorridorBalconyPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -3, 0, 0, 9, 7, 9, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorBalcony, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for CorridorBalconyPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + + // In Java: `i` is 1 for EAST/SOUTH facing, 5 for WEST/NORTH. + let i = match self.piece.piece.facing { + Some(BlockDirection::West) | Some(BlockDirection::North) => 5, + _ => 1, + }; + + let nw_blocked = random.next_bounded_i32(8) > 0; + let se_blocked = random.next_bounded_i32(8) > 0; + + self.piece.fill_nw_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + i, + nw_blocked, + pieces_to_process, + ); + self.piece.fill_se_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + i, + se_blocked, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ns = fence(false, false, true, true); + let f_ew = fence(true, true, false, false); + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 8, 1, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 8, 5, 8, air, air); + p.fill_with_outline(chunk, &bb, false, 0, 6, 0, 8, 6, 5, nb, nb); + + // Front wall (z=0) openings + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 2, 5, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 2, 0, 8, 5, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 3, 0, 1, 4, 0, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 7, 3, 0, 7, 4, 0, f_ew, f_ew); + + // Back floor and openings + p.fill_with_outline(chunk, &bb, false, 0, 2, 4, 8, 2, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 1, 4, 2, 2, 4, air, air); + p.fill_with_outline(chunk, &bb, false, 6, 1, 4, 7, 2, 4, air, air); + + // Back fence railing + p.fill_with_outline(chunk, &bb, false, 1, 3, 8, 7, 3, 8, f_ew, f_ew); + p.add_block(chunk, fence(false, true, false, true), 0, 3, 8, &bb); + p.add_block(chunk, fence(true, false, false, true), 8, 3, 8, &bb); + p.fill_with_outline(chunk, &bb, false, 0, 3, 6, 0, 3, 7, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 8, 3, 6, 8, 3, 7, f_ns, f_ns); + + // Side wall pillars and fences + p.fill_with_outline(chunk, &bb, false, 0, 3, 4, 0, 5, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 8, 3, 4, 8, 5, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 3, 5, 2, 5, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 3, 5, 7, 5, 5, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 4, 5, 1, 5, 5, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 7, 4, 5, 7, 5, 5, f_ew, f_ew); + + for i in 0..=5i32 { + for j in 0..=8i32 { + p.fill_downwards(chunk, nb, j, -1, i, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_crossing.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_crossing.rs new file mode 100644 index 000000000..df250eb6b --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_crossing.rs @@ -0,0 +1,137 @@ +use pumpkin_data::Block; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// 4-way interior corridor crossing (5 × 7 × 5). +#[derive(Clone)] +pub struct CorridorCrossingPiece { + pub piece: NetherFortressPiece, +} + +impl CorridorCrossingPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -1, 0, 0, 5, 7, 5, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorCrossing, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for CorridorCrossingPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 1, + 0, + true, + pieces_to_process, + ); + self.piece.fill_nw_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + 1, + true, + pieces_to_process, + ); + self.piece.fill_se_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + 1, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 4, 1, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 4, 5, 4, air, air); + + // Four corner pillars + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 0, 5, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 2, 0, 4, 5, 0, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 4, 0, 5, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 2, 4, 4, 5, 4, nb, nb); + + // Ceiling + p.fill_with_outline(chunk, &bb, false, 0, 6, 0, 4, 6, 4, nb, nb); + + for i in 0..=4i32 { + for j in 0..=4i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs new file mode 100644 index 000000000..2aa2f08bc --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs @@ -0,0 +1,236 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Large entrance tower connecting the bridge network to the interior (13 × 14 × 13). +#[derive(Clone)] +pub struct CorridorExitPiece { + pub piece: NetherFortressPiece, +} + +impl CorridorExitPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + _random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -5, -3, 0, 13, 14, 13, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorExit, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for CorridorExitPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 5, + 3, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + + // Main shell + p.fill_with_outline(chunk, &bb, false, 0, 3, 0, 12, 4, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 5, 0, 12, 13, 12, air, air); + + // Outer walls + p.fill_with_outline(chunk, &bb, false, 0, 5, 0, 1, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 11, 5, 0, 12, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 11, 4, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 8, 5, 11, 10, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 9, 11, 7, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 0, 4, 12, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 8, 5, 0, 10, 12, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 9, 0, 7, 12, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 11, 2, 10, 12, 10, nb, nb); + + // Gate arch fence + p.fill_with_outline( + chunk, + &bb, + false, + 5, + 8, + 0, + 7, + 8, + 0, + Block::NETHER_BRICK_FENCE.default_state, + Block::NETHER_BRICK_FENCE.default_state, + ); + + // Battlement fences and corner blocks along top + let mut i = 1i32; + while i <= 11 { + p.fill_with_outline(chunk, &bb, false, i, 10, 0, i, 11, 0, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, i, 10, 12, i, 11, 12, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 0, 10, i, 0, 11, i, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 12, 10, i, 12, 11, i, f_ns, f_ns); + p.add_block(chunk, nb, i, 13, 0, &bb); + p.add_block(chunk, nb, i, 13, 12, &bb); + p.add_block(chunk, nb, 0, 13, i, &bb); + p.add_block(chunk, nb, 12, 13, i, &bb); + if i != 11 { + p.add_block(chunk, f_ew, i + 1, 13, 0, &bb); + p.add_block(chunk, f_ew, i + 1, 13, 12, &bb); + p.add_block(chunk, f_ns, 0, 13, i + 1, &bb); + p.add_block(chunk, f_ns, 12, 13, i + 1, &bb); + } + i += 2; + } + + // Corner fence pieces + p.add_block(chunk, fence(false, true, true, false), 0, 13, 0, &bb); + p.add_block(chunk, fence(false, true, false, true), 0, 13, 12, &bb); + p.add_block(chunk, fence(true, false, false, true), 12, 13, 12, &bb); + p.add_block(chunk, fence(true, false, true, false), 12, 13, 0, &bb); + + // Interior buttress fences + let mut i = 3i32; + while i <= 9 { + p.fill_with_outline( + chunk, + &bb, + false, + 1, + 7, + i, + 1, + 8, + i, + fence(false, false, true, true), // + west connection + fence(false, false, true, true), + ); + p.fill_with_outline( + chunk, + &bb, + false, + 11, + 7, + i, + 11, + 8, + i, + fence(false, false, true, true), // + east connection + fence(false, false, true, true), + ); + i += 2; + } + + // Exterior bridge supports + p.fill_with_outline(chunk, &bb, false, 4, 2, 0, 8, 2, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 4, 12, 2, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 0, 0, 8, 1, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 0, 9, 8, 1, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 0, 4, 3, 1, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 9, 0, 4, 12, 1, 8, nb, nb); + + for i in 4..=8i32 { + for j in 0..=2i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + p.fill_downwards(chunk, nb, i, -1, 12 - j, &bb); + } + } + for i in 0..=2i32 { + for j in 4..=8i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + p.fill_downwards(chunk, nb, 12 - i, -1, j, &bb); + } + } + + // Central lava well + p.fill_with_outline(chunk, &bb, false, 5, 5, 5, 7, 5, 7, nb, nb); + p.fill_with_outline(chunk, &bb, false, 6, 1, 6, 6, 4, 6, air, air); + p.add_block(chunk, nb, 6, 0, 6, &bb); + p.add_block(chunk, Block::LAVA.default_state, 6, 5, 6, &bb); + + // Schedule lava fluid tick + // TODO + // let lava_pos = p.offset_pos(6, 5, 6); + // if bb.contains_pos(&lava_pos) { + // chunk.schedule_fluid_tick(&lava_pos, 0); + // } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_left_turn.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_left_turn.rs new file mode 100644 index 000000000..d74456243 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_left_turn.rs @@ -0,0 +1,154 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Interior corridor that turns left (NW), optionally containing a chest (5 × 7 × 5). +#[derive(Clone)] +pub struct CorridorLeftTurnPiece { + pub piece: NetherFortressPiece, + pub contains_chest: bool, +} + +impl CorridorLeftTurnPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -1, 0, 0, 5, 7, 5, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let contains_chest = random.next_bounded_i32(3) == 0; + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorLeftTurn, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { + piece, + contains_chest, + })) + } +} + +impl StructurePieceBase for CorridorLeftTurnPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_nw_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + 1, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 4, 1, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 4, 5, 4, air, air); + + // Right wall (closed) + p.fill_with_outline(chunk, &bb, false, 4, 2, 0, 4, 5, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 3, 1, 4, 4, 1, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 4, 3, 3, 4, 4, 3, f_ns, f_ns); + + // Front wall (closed) + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 0, 5, 0, nb, nb); + + // Back wall + p.fill_with_outline(chunk, &bb, false, 0, 2, 4, 3, 5, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 3, 4, 1, 4, 4, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 3, 3, 4, 3, 4, 4, f_ew, f_ew); + + // Optional chest + // TODO + // if self.contains_chest { + // let chest_pos = p.offset_pos(3, 2, 3); + // if bb.contains_pos(&chest_pos) { + // self.contains_chest = false; + // p.add_chest(chunk, &bb, random, 3, 2, 3, "nether_bridge"); + // } + // } + + // Ceiling + p.fill_with_outline(chunk, &bb, false, 0, 6, 0, 4, 6, 4, nb, nb); + + for i in 0..=4i32 { + for j in 0..=4i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_nether_warts_room.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_nether_warts_room.rs new file mode 100644 index 000000000..c47582cb3 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_nether_warts_room.rs @@ -0,0 +1,281 @@ +use pumpkin_data::{ + Block, + block_properties::{ + BlockProperties, HorizontalFacing, OakFenceLikeProperties, OakStairsLikeProperties, + }, +}; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Large interior room with nether wart crops (13 × 14 × 13). +#[derive(Clone)] +pub struct CorridorNetherWartsRoomPiece { + pub piece: NetherFortressPiece, +} + +impl CorridorNetherWartsRoomPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -5, -3, 0, 13, 14, 13, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorNetherWartsRoom, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for CorridorNetherWartsRoomPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + // Two forward exits (one at the back, one even further) + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 5, + 3, + true, + pieces_to_process, + ); + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 5, + 11, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + let f_ns_west = fence(true, false, true, true); + let f_ns_east = fence(false, true, true, true); + + // Shell (identical to CorridorExit outer shape) + p.fill_with_outline(chunk, &bb, false, 0, 3, 0, 12, 4, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 5, 0, 12, 13, 12, air, air); + p.fill_with_outline(chunk, &bb, false, 0, 5, 0, 1, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 11, 5, 0, 12, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 11, 4, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 8, 5, 11, 10, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 9, 11, 7, 12, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 0, 4, 12, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 8, 5, 0, 10, 12, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 5, 9, 0, 7, 12, 1, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 11, 2, 10, 12, 10, nb, nb); + + // Battlements + let mut i = 1i32; + while i <= 11 { + p.fill_with_outline(chunk, &bb, false, i, 10, 0, i, 11, 0, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, i, 10, 12, i, 11, 12, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 0, 10, i, 0, 11, i, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 12, 10, i, 12, 11, i, f_ns, f_ns); + p.add_block(chunk, nb, i, 13, 0, &bb); + p.add_block(chunk, nb, i, 13, 12, &bb); + p.add_block(chunk, nb, 0, 13, i, &bb); + p.add_block(chunk, nb, 12, 13, i, &bb); + if i != 11 { + p.add_block(chunk, f_ew, i + 1, 13, 0, &bb); + p.add_block(chunk, f_ew, i + 1, 13, 12, &bb); + p.add_block(chunk, f_ns, 0, 13, i + 1, &bb); + p.add_block(chunk, f_ns, 12, 13, i + 1, &bb); + } + i += 2; + } + p.add_block(chunk, fence(false, true, true, false), 0, 13, 0, &bb); + p.add_block(chunk, fence(false, true, false, true), 0, 13, 12, &bb); + p.add_block(chunk, fence(true, false, false, true), 12, 13, 12, &bb); + p.add_block(chunk, fence(true, false, true, false), 12, 13, 0, &bb); + + // Interior buttress fences + let mut i = 3i32; + while i <= 9 { + p.fill_with_outline(chunk, &bb, false, 1, 7, i, 1, 8, i, f_ns_west, f_ns_west); + p.fill_with_outline(chunk, &bb, false, 11, 7, i, 11, 8, i, f_ns_east, f_ns_east); + i += 2; + } + + // Staircase going up (north-facing stairs, going from z=4 up to z=10) + let mut stair_props = OakStairsLikeProperties::default(&Block::NETHER_BRICK_STAIRS); + stair_props.facing = HorizontalFacing::North; + let stair_n = + pumpkin_data::BlockState::from_id(stair_props.to_state_id(&Block::NETHER_BRICK_STAIRS)); + + stair_props.facing = HorizontalFacing::East; + let stair_e = + pumpkin_data::BlockState::from_id(stair_props.to_state_id(&Block::NETHER_BRICK_STAIRS)); + + stair_props.facing = HorizontalFacing::West; + let stair_w = + pumpkin_data::BlockState::from_id(stair_props.to_state_id(&Block::NETHER_BRICK_STAIRS)); + + for j in 0..=6i32 { + let k = j + 4; + for l in 5..=7i32 { + p.add_block(chunk, stair_n, l, 5 + j, k, &bb); + } + if (5..=8).contains(&k) { + p.fill_with_outline(chunk, &bb, false, 5, 5, k, 7, j + 4, k, nb, nb); + } else if (9..=10).contains(&k) { + p.fill_with_outline(chunk, &bb, false, 5, 8, k, 7, j + 4, k, nb, nb); + } + if j >= 1 { + p.fill_with_outline(chunk, &bb, false, 5, 6 + j, k, 7, 9 + j, k, air, air); + } + } + for l in 5..=7i32 { + p.add_block(chunk, stair_n, l, 12, 11, &bb); + } + + // Stair balcony fences + p.fill_with_outline(chunk, &bb, false, 5, 6, 7, 5, 7, 7, f_ns_east, f_ns_east); + p.fill_with_outline(chunk, &bb, false, 7, 6, 7, 7, 7, 7, f_ns_west, f_ns_west); + + // Clear the arch above the top stair exit + p.fill_with_outline(chunk, &bb, false, 5, 13, 12, 7, 13, 12, air, air); + + // Wart beds (raised platforms of soul sand with nether wart on top) + p.fill_with_outline(chunk, &bb, false, 2, 5, 2, 3, 5, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 9, 3, 5, 10, nb, nb); + p.fill_with_outline(chunk, &bb, false, 2, 5, 4, 2, 5, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 9, 5, 2, 10, 5, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 9, 5, 9, 10, 5, 10, nb, nb); + p.fill_with_outline(chunk, &bb, false, 10, 5, 4, 10, 5, 8, nb, nb); + + // Stair edgings for the wart beds + p.add_block(chunk, stair_w, 4, 5, 2, &bb); + p.add_block(chunk, stair_w, 4, 5, 3, &bb); + p.add_block(chunk, stair_w, 4, 5, 9, &bb); + p.add_block(chunk, stair_w, 4, 5, 10, &bb); + p.add_block(chunk, stair_e, 8, 5, 2, &bb); + p.add_block(chunk, stair_e, 8, 5, 3, &bb); + p.add_block(chunk, stair_e, 8, 5, 9, &bb); + p.add_block(chunk, stair_e, 8, 5, 10, &bb); + + let soul_sand = Block::SOUL_SAND.default_state; + let nether_wart = Block::NETHER_WART.default_state; + + p.fill_with_outline(chunk, &bb, false, 3, 4, 4, 4, 4, 8, soul_sand, soul_sand); + p.fill_with_outline(chunk, &bb, false, 8, 4, 4, 9, 4, 8, soul_sand, soul_sand); + p.fill_with_outline( + chunk, + &bb, + false, + 3, + 5, + 4, + 4, + 5, + 8, + nether_wart, + nether_wart, + ); + p.fill_with_outline( + chunk, + &bb, + false, + 8, + 5, + 4, + 9, + 5, + 8, + nether_wart, + nether_wart, + ); + + // Bridge support legs + p.fill_with_outline(chunk, &bb, false, 4, 2, 0, 8, 2, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 4, 12, 2, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 0, 0, 8, 1, 3, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 0, 9, 8, 1, 12, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 0, 4, 3, 1, 8, nb, nb); + p.fill_with_outline(chunk, &bb, false, 9, 0, 4, 12, 1, 8, nb, nb); + + for l in 4..=8i32 { + for m in 0..=2i32 { + p.fill_downwards(chunk, nb, l, -1, m, &bb); + p.fill_downwards(chunk, nb, l, -1, 12 - m, &bb); + } + } + for l in 0..=2i32 { + for m in 4..=8i32 { + p.fill_downwards(chunk, nb, l, -1, m, &bb); + p.fill_downwards(chunk, nb, 12 - l, -1, m, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_right_turn.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_right_turn.rs new file mode 100644 index 000000000..588e078e4 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_right_turn.rs @@ -0,0 +1,154 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{ + BlockDirection, + math::block_box::BlockBox, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Interior corridor that turns right (SE), optionally containing a chest (5 × 7 × 5). +#[derive(Clone)] +pub struct CorridorRightTurnPiece { + pub piece: NetherFortressPiece, + pub contains_chest: bool, +} + +impl CorridorRightTurnPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -1, 0, 0, 5, 7, 5, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let contains_chest = random.next_bounded_i32(3) == 0; + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorRightTurn, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { + piece, + contains_chest, + })) + } +} + +impl StructurePieceBase for CorridorRightTurnPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_se_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 0, + 1, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + let fence = |west: bool, east: bool, north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.west = west; + props.east = east; + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ew = fence(true, true, false, false); + let f_ns = fence(false, false, true, true); + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 4, 1, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 4, 5, 4, air, air); + + // Left wall (closed) + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 0, 5, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 3, 1, 0, 4, 1, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 0, 3, 3, 0, 4, 3, f_ns, f_ns); + + // Front wall (closed) + p.fill_with_outline(chunk, &bb, false, 4, 2, 0, 4, 5, 0, nb, nb); + + // Back wall (SE opening side) + p.fill_with_outline(chunk, &bb, false, 1, 2, 4, 4, 5, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 1, 3, 4, 1, 4, 4, f_ew, f_ew); + p.fill_with_outline(chunk, &bb, false, 3, 3, 4, 3, 4, 4, f_ew, f_ew); + + // Optional chest + // TODO + // if self.contains_chest { + // let chest_pos = p.offset_pos(1, 2, 3); + // if bb.contains_pos(&chest_pos) { + // self.contains_chest = false; + // p.add_chest(chunk, &bb, random, 1, 2, 3, "nether_bridge"); + // } + // } + + // Ceiling + p.fill_with_outline(chunk, &bb, false, 0, 6, 0, 4, 6, 4, nb, nb); + + for i in 0..=4i32 { + for j in 0..=4i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_stairs.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_stairs.rs new file mode 100644 index 000000000..f529bb2fe --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_stairs.rs @@ -0,0 +1,152 @@ +use pumpkin_data::{ + Block, + block_properties::{ + BlockProperties, HorizontalFacing, OakFenceLikeProperties, OakStairsLikeProperties, + }, +}; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Descending interior corridor staircase (5 × 14 × 10). +#[derive(Clone)] +pub struct CorridorStairsPiece { + pub piece: NetherFortressPiece, +} + +impl CorridorStairsPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + // Offset: -1 left/right, -7 height (deep stairs), 14 tall, 10 long + let bbox = BlockBox::rotated(x, y, z, -1, -7, 0, 5, 14, 10, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressCorridorStairs, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for CorridorStairsPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 1, + 0, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + // Stair block facing south (going deeper into the structure) + let mut stair_props = OakStairsLikeProperties::default(&Block::NETHER_BRICK_STAIRS); + stair_props.facing = HorizontalFacing::South; + let stair = + pumpkin_data::BlockState::from_id(stair_props.to_state_id(&Block::NETHER_BRICK_STAIRS)); + + let fence = |north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ns = fence(true, true); + + for i in 0..=9i32 { + let j = 1.max(7 - i); + let k = (j + 5).min(14 - i).min(13); + let l = i; + + // Solid base + p.fill_with_outline(chunk, &bb, false, 0, 0, i, 4, j, i, nb, nb); + + // Air interior + p.fill_with_outline(chunk, &bb, false, 1, j + 1, i, 3, k - 1, i, air, air); + + // Stair treads (only for the descending section) + if i <= 6 { + p.add_block(chunk, stair, 1, j + 1, i, &bb); + p.add_block(chunk, stair, 2, j + 1, i, &bb); + p.add_block(chunk, stair, 3, j + 1, i, &bb); + } + + // Ceiling row + p.fill_with_outline(chunk, &bb, false, 0, k, i, 4, k, i, nb, nb); + + // Side walls + p.fill_with_outline(chunk, &bb, false, 0, j + 1, i, 0, k - 1, i, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, j + 1, i, 4, k - 1, i, nb, nb); + + // Fence railings every 2 slices + if (i & 1) == 0 { + p.fill_with_outline(chunk, &bb, false, 0, j + 2, i, 0, j + 3, i, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 4, j + 2, i, 4, j + 3, i, f_ns, f_ns); + } + + // Fill downwards beneath each slice + for m in 0..=4i32 { + p.fill_downwards(chunk, nb, m, -1, l, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs new file mode 100644 index 000000000..bd1086811 --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs @@ -0,0 +1,624 @@ +use std::sync::{Arc, Mutex}; + +use pumpkin_util::{ + BlockDirection, + math::{block_box::BlockBox, position::BlockPos}, + random::RandomImpl, +}; +use serde::Deserialize; + +use crate::generation::{ + section_coords, + structure::structures::{ + StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePiecesCollector, + StructurePosition, nether_fortress::bridge_crossing::BridgeCrossingPiece, + }, +}; + +pub mod bridge; +pub mod bridge_crossing; +pub mod bridge_end; +pub mod bridge_platform; +pub mod bridge_small_crossing; +pub mod bridge_stairs; +pub mod corridor_balcony; +pub mod corridor_crossing; +pub mod corridor_exit; +pub mod corridor_left_turn; +pub mod corridor_nether_warts_room; +pub mod corridor_right_turn; +pub mod corridor_stairs; +pub mod small_corridor; + +/// Great reference: +#[derive(Deserialize)] +pub struct NetherFortressGenerator; + +impl StructureGenerator for NetherFortressGenerator { + fn get_structure_position( + &self, + context: StructureGeneratorContext, + ) -> Option { + let mut collector = StructurePiecesCollector::default(); + let mut random = context.random; + + let start_x = section_coords::section_to_block(context.chunk_x) + 2; + let start_z = section_coords::section_to_block(context.chunk_z) + 2; + + let start_piece = BridgeCrossingPiece::new_start(&mut random, start_x, start_z); + let start_box: Box = + Box::new(start_piece.clone()); + + let mut pieces_to_process: Vec< + Box, + > = Vec::new(); + + let mut bridge_pieces = get_bridge_piece_weights(); + let mut corridor_pieces = get_corridor_piece_weights(); + + collector.add_piece(start_box); + + start_piece.fill_openings( + &start_piece.piece.piece, + &mut random, + &mut bridge_pieces, + &mut corridor_pieces, + &mut collector, + &mut pieces_to_process, + ); + + while !pieces_to_process.is_empty() { + let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize; + let piece = pieces_to_process.remove(idx); + piece.fill_openings_nether( + &start_piece.piece.piece, + &mut random, + &mut bridge_pieces, + &mut corridor_pieces, + &mut collector, + &mut pieces_to_process, + ); + } + + Some(StructurePosition { + start_pos: BlockPos::new( + section_coords::section_to_block(context.chunk_x), + 64, + section_coords::section_to_block(context.chunk_z), + ), + collector: Arc::new(Mutex::new(collector)), + }) + } +} + +/// Weights for the bridge (exterior) pieces. +#[derive(Clone, Debug)] +pub struct PieceWeight { + pub piece_type: NetherFortressPieceType, + pub weight: i32, + pub limit: u32, + pub generated_count: u32, + pub repeatable: bool, +} + +impl PieceWeight { + const fn new( + piece_type: NetherFortressPieceType, + weight: i32, + limit: u32, + repeatable: bool, + ) -> Self { + Self { + piece_type, + weight, + limit, + generated_count: 0, + repeatable, + } + } + + pub const fn can_generate(&self) -> bool { + self.limit == 0 || self.generated_count < self.limit + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NetherFortressPieceType { + // Bridge pieces + Bridge, + BridgeCrossing, + BridgeSmallCrossing, + BridgeStairs, + BridgePlatform, + CorridorExit, + // Corridor pieces + SmallCorridor, + CorridorCrossing, + CorridorRightTurn, + CorridorLeftTurn, + CorridorStairs, + CorridorBalcony, + CorridorNetherWartsRoom, +} + +pub fn get_bridge_piece_weights() -> Vec { + vec![ + PieceWeight::new(NetherFortressPieceType::Bridge, 30, 0, true), + PieceWeight::new(NetherFortressPieceType::BridgeCrossing, 10, 4, false), + PieceWeight::new(NetherFortressPieceType::BridgeSmallCrossing, 10, 4, false), + PieceWeight::new(NetherFortressPieceType::BridgeStairs, 10, 3, false), + PieceWeight::new(NetherFortressPieceType::BridgePlatform, 5, 2, false), + PieceWeight::new(NetherFortressPieceType::CorridorExit, 5, 1, false), + ] +} + +pub fn get_corridor_piece_weights() -> Vec { + vec![ + PieceWeight::new(NetherFortressPieceType::SmallCorridor, 25, 0, true), + PieceWeight::new(NetherFortressPieceType::CorridorCrossing, 15, 5, false), + PieceWeight::new(NetherFortressPieceType::CorridorRightTurn, 5, 10, false), + PieceWeight::new(NetherFortressPieceType::CorridorLeftTurn, 5, 10, false), + PieceWeight::new(NetherFortressPieceType::CorridorStairs, 10, 3, true), + PieceWeight::new(NetherFortressPieceType::CorridorBalcony, 7, 2, false), + PieceWeight::new( + NetherFortressPieceType::CorridorNetherWartsRoom, + 5, + 2, + false, + ), + ] +} + +/// The shared base state for every nether fortress piece. +#[derive(Clone)] +pub struct NetherFortressPiece { + pub piece: StructurePiece, +} + +impl NetherFortressPiece { + #[must_use] + pub const fn new( + r#type: crate::generation::structure::piece::StructurePieceType, + chain_length: u32, + bbox: BlockBox, + ) -> Self { + Self { + piece: StructurePiece::new(r#type, bbox, chain_length), + } + } + + pub const fn is_in_bounds(bb: &BlockBox) -> bool { + bb.min.y > 10 + } + + /// Attempt to pick a random piece from `weights` up to 5 times. + /// Falls back to `BridgeEnd` if nothing succeeds. + #[allow(clippy::too_many_arguments)] + pub fn pick_piece( + weights: &mut Vec, + last_piece: &mut Option, + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let total_weight = Self::check_remaining_pieces(weights); + if total_weight < 0 { + return bridge_end::BridgeEndPiece::create( + collector, + random, + x, + y, + z, + orientation, + chain_length, + ); + } + + let mut attempt = 0; + while attempt < 5 { + attempt += 1; + let mut k = random.next_bounded_i32(total_weight); + + for i in 0..weights.len() { + k -= weights[i].weight; + if k < 0 { + let same_as_last = + Some(weights[i].piece_type) == *last_piece && !weights[i].repeatable; + if !weights[i].can_generate() || same_as_last { + break; + } + + if let Some(p) = Self::create_piece( + weights[i].piece_type, + collector, + random, + x, + y, + z, + orientation, + chain_length, + ) { + weights[i].generated_count += 1; + *last_piece = Some(weights[i].piece_type); + + if !weights[i].can_generate() { + weights.remove(i); + } + + return Some(p); + } + break; + } + } + } + + bridge_end::BridgeEndPiece::create(collector, random, x, y, z, orientation, chain_length) + } + + fn check_remaining_pieces(weights: &[PieceWeight]) -> i32 { + let mut has_limited = false; + let mut total = 0i32; + for w in weights { + if w.limit > 0 && w.generated_count < w.limit { + has_limited = true; + } + total += w.weight; + } + if has_limited { total } else { -1 } + } + + #[allow(clippy::too_many_arguments)] + pub fn create_piece( + piece_type: NetherFortressPieceType, + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + match piece_type { + NetherFortressPieceType::Bridge => { + bridge::BridgePiece::create(collector, random, x, y, z, orientation, chain_length) + } + NetherFortressPieceType::BridgeCrossing => { + bridge_crossing::BridgeCrossingPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::BridgeSmallCrossing => { + bridge_small_crossing::BridgeSmallCrossingPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::BridgeStairs => bridge_stairs::BridgeStairsPiece::create( + collector, + x, + y, + z, + chain_length, + orientation, + ), + NetherFortressPieceType::BridgePlatform => { + bridge_platform::BridgePlatformPiece::create( + collector, + x, + y, + z, + chain_length, + orientation, + ) + } + NetherFortressPieceType::CorridorExit => corridor_exit::CorridorExitPiece::create( + collector, + random, + x, + y, + z, + orientation, + chain_length, + ), + NetherFortressPieceType::SmallCorridor => small_corridor::SmallCorridorPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ), + NetherFortressPieceType::CorridorCrossing => { + corridor_crossing::CorridorCrossingPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::CorridorRightTurn => { + corridor_right_turn::CorridorRightTurnPiece::create( + collector, + random, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::CorridorLeftTurn => { + corridor_left_turn::CorridorLeftTurnPiece::create( + collector, + random, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::CorridorStairs => { + corridor_stairs::CorridorStairsPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::CorridorBalcony => { + corridor_balcony::CorridorBalconyPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ) + } + NetherFortressPieceType::CorridorNetherWartsRoom => { + corridor_nether_warts_room::CorridorNetherWartsRoomPiece::create( + collector, + x, + y, + z, + orientation, + chain_length, + ) + } + } + } + + // ---- opening helpers -------------------------------------------------- + + #[allow(clippy::too_many_arguments)] + pub fn fill_forward_opening( + &self, + start: &StructurePiece, + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + last_piece: &mut Option, + left_right_offset: i32, + height_offset: i32, + inside: bool, + pieces_to_process: &mut Vec< + Box, + >, + ) { + if let Some(facing) = self.piece.facing { + let bb = self.piece.bounding_box; + let (nx, ny, nz) = match facing { + BlockDirection::North => ( + bb.min.x + left_right_offset, + bb.min.y + height_offset, + bb.min.z - 1, + ), + BlockDirection::South => ( + bb.min.x + left_right_offset, + bb.min.y + height_offset, + bb.max.z + 1, + ), + BlockDirection::West => ( + bb.min.x - 1, + bb.min.y + height_offset, + bb.min.z + left_right_offset, + ), + BlockDirection::East => ( + bb.max.x + 1, + bb.min.y + height_offset, + bb.min.z + left_right_offset, + ), + _ => return, + }; + self.piece_generator( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + last_piece, + nx, + ny, + nz, + facing, + inside, + pieces_to_process, + ); + } + } + + #[allow(clippy::too_many_arguments)] + pub fn fill_nw_opening( + &self, + start: &StructurePiece, + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + last_piece: &mut Option, + height_offset: i32, + left_right_offset: i32, + inside: bool, + pieces_to_process: &mut Vec< + Box, + >, + ) { + if let Some(facing) = self.piece.facing { + let bb = self.piece.bounding_box; + let (nx, ny, nz, next_facing) = match facing { + BlockDirection::North | BlockDirection::South => ( + bb.min.x - 1, + bb.min.y + height_offset, + bb.min.z + left_right_offset, + BlockDirection::West, + ), + BlockDirection::West | BlockDirection::East => ( + bb.min.x + left_right_offset, + bb.min.y + height_offset, + bb.min.z - 1, + BlockDirection::North, + ), + _ => return, + }; + self.piece_generator( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + last_piece, + nx, + ny, + nz, + next_facing, + inside, + pieces_to_process, + ); + } + } + + #[allow(clippy::too_many_arguments)] + pub fn fill_se_opening( + &self, + start: &StructurePiece, + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + last_piece: &mut Option, + height_offset: i32, + left_right_offset: i32, + inside: bool, + pieces_to_process: &mut Vec< + Box, + >, + ) { + if let Some(facing) = self.piece.facing { + let bb = self.piece.bounding_box; + let (nx, ny, nz, next_facing) = match facing { + BlockDirection::North | BlockDirection::South => ( + bb.max.x + 1, + bb.min.y + height_offset, + bb.min.z + left_right_offset, + BlockDirection::East, + ), + BlockDirection::West | BlockDirection::East => ( + bb.min.x + left_right_offset, + bb.min.y + height_offset, + bb.max.z + 1, + BlockDirection::South, + ), + _ => return, + }; + self.piece_generator( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + last_piece, + nx, + ny, + nz, + next_facing, + inside, + pieces_to_process, + ); + } + } + + #[allow(clippy::too_many_arguments)] + fn piece_generator( + &self, + start: &StructurePiece, + collector: &mut StructurePiecesCollector, + random: &mut impl RandomImpl, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + last_piece: &mut Option, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + inside: bool, + pieces_to_process: &mut Vec< + Box, + >, + ) { + let start_box = start.bounding_box; + if (x - start_box.min.x).abs() > 112 || (z - start_box.min.z).abs() > 112 { + if let Some(p) = bridge_end::BridgeEndPiece::create( + collector, + random, + x, + y, + z, + orientation, + self.piece.chain_length, + ) { + collector.add_piece(p) + } + return; + } + + let weights = if inside { + corridor_pieces + } else { + bridge_pieces + }; + + if let Some(p) = Self::pick_piece( + weights, + last_piece, + collector, + random, + x, + y, + z, + orientation, + self.piece.chain_length + 1, + ) { + collector.add_piece(p.clone_box()); + pieces_to_process.push(p); + } + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fortress/small_corridor.rs b/pumpkin-world/src/generation/structure/structures/nether_fortress/small_corridor.rs new file mode 100644 index 000000000..0c547d2ee --- /dev/null +++ b/pumpkin-world/src/generation/structure/structures/nether_fortress/small_corridor.rs @@ -0,0 +1,129 @@ +use pumpkin_data::{ + Block, + block_properties::{BlockProperties, OakFenceLikeProperties}, +}; +use pumpkin_util::{BlockDirection, math::block_box::BlockBox, random::RandomGenerator}; + +use crate::{ + ProtoChunk, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructurePiece, StructurePieceBase, StructurePiecesCollector, + nether_fortress::{NetherFortressPiece, NetherFortressPieceType, PieceWeight}, + }, + }, +}; + +/// Straight interior corridor segment (5 × 7 × 5). +/// This is also the fallback piece returned by the generation logic. +#[derive(Clone)] +pub struct SmallCorridorPiece { + pub piece: NetherFortressPiece, +} + +impl SmallCorridorPiece { + pub fn create( + collector: &mut StructurePiecesCollector, + x: i32, + y: i32, + z: i32, + orientation: BlockDirection, + chain_length: u32, + ) -> Option> { + let bbox = BlockBox::rotated(x, y, z, -1, 0, 0, 5, 7, 5, &orientation); + if !NetherFortressPiece::is_in_bounds(&bbox) || collector.get_intersecting(&bbox).is_some() + { + return None; + } + + let mut piece = NetherFortressPiece::new( + StructurePieceType::NetherFortressSmallCorridor, + chain_length, + bbox, + ); + piece.piece.set_facing(Some(orientation)); + Some(Box::new(Self { piece })) + } +} + +impl StructurePieceBase for SmallCorridorPiece { + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece.piece + } + + fn fill_openings_nether( + &self, + start: &StructurePiece, + random: &mut RandomGenerator, + bridge_pieces: &mut Vec, + corridor_pieces: &mut Vec, + collector: &mut StructurePiecesCollector, + pieces_to_process: &mut Vec>, + ) { + let mut last: Option = None; + self.piece.fill_forward_opening( + start, + collector, + random, + bridge_pieces, + corridor_pieces, + &mut last, + 1, + 0, + true, + pieces_to_process, + ); + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = *chunk_box; + let p = &self.piece.piece; + let nb = Block::NETHER_BRICKS.default_state; + let air = Block::AIR.default_state; + + let fence = |north: bool, south: bool| { + let mut props = OakFenceLikeProperties::default(&Block::NETHER_BRICK_FENCE); + props.north = north; + props.south = south; + pumpkin_data::BlockState::from_id(props.to_state_id(&Block::NETHER_BRICK_FENCE)) + }; + let f_ns = fence(true, true); + + p.fill_with_outline(chunk, &bb, false, 0, 0, 0, 4, 1, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 4, 5, 4, air, air); + + // Side walls + p.fill_with_outline(chunk, &bb, false, 0, 2, 0, 0, 5, 4, nb, nb); + p.fill_with_outline(chunk, &bb, false, 4, 2, 0, 4, 5, 4, nb, nb); + + // Fence railings on side walls + p.fill_with_outline(chunk, &bb, false, 0, 3, 1, 0, 4, 1, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 0, 3, 3, 0, 4, 3, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 4, 3, 1, 4, 4, 1, f_ns, f_ns); + p.fill_with_outline(chunk, &bb, false, 4, 3, 3, 4, 4, 3, f_ns, f_ns); + + // Ceiling + p.fill_with_outline(chunk, &bb, false, 0, 6, 0, 4, 6, 4, nb, nb); + + for i in 0..=4i32 { + for j in 0..=4i32 { + p.fill_downwards(chunk, nb, i, -1, j, &bb); + } + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/pumpkin-world/src/generation/structure/structures/nether_fossil.rs b/pumpkin-world/src/generation/structure/structures/nether_fossil.rs index e1cf1b48b..cce1ad1bd 100644 --- a/pumpkin-world/src/generation/structure/structures/nether_fossil.rs +++ b/pumpkin-world/src/generation/structure/structures/nether_fossil.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_util::{ - math::{position::BlockPos, vector3::Vector3}, + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, random::{RandomDeriverImpl, RandomGenerator, RandomImpl}, }; use tracing::debug; @@ -166,7 +166,13 @@ impl StructurePieceBase for NetherFossilPiece { Box::new(self.clone()) } - fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + seed: i64, + _chunk_box: &BlockBox, + ) { // Vanilla column scan: find air above soul sand or solid block let Some(placement_y) = self.find_placement_y(chunk) else { debug!( diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs b/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs index 3be76510f..499a5e7e3 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/chest_corridor.rs @@ -91,9 +91,15 @@ impl StructurePieceBase for ChestCorridorPiece { ); } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs b/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs index 4af8a4d80..bd17fdaeb 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/corridor.rs @@ -119,9 +119,15 @@ impl StructurePieceBase for CorridorPiece { } } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; let air = Block::AIR.default_state; @@ -246,8 +252,14 @@ impl StructurePieceBase for SmallCorridorPiece { &mut self.piece.piece } - fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { - let box_limit = self.piece.piece.bounding_box; + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let box_limit = *chunk_box; let p = &self.piece.piece; let stone = Block::STONE_BRICKS.default_state; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs b/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs index 7ab7b1bd9..705959dc5 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/five_way_crossing.rs @@ -159,9 +159,15 @@ impl StructurePieceBase for FiveWayCrossingPiece { } } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; let air = Block::AIR.default_state; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs b/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs index a4ba3db44..aed434d79 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/left_turn.rs @@ -101,9 +101,15 @@ impl StructurePieceBase for LeftTurnPiece { } } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; let air = Block::AIR.default_state; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/library.rs b/pumpkin-world/src/generation/structure/structures/stronghold/library.rs index b7c61dc26..01e45c70b 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/library.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/library.rs @@ -71,9 +71,15 @@ impl StructurePieceBase for LibraryPiece { &mut self.piece.piece } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; 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 b3ca51cf5..a33036d21 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs @@ -87,9 +87,15 @@ impl StructurePieceBase for PortalRoomPiece { *has_portal_room = true; } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs b/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs index f8ef6e067..a6673b3c0 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/prison_hall.rs @@ -92,9 +92,15 @@ impl StructurePieceBase for PrisonHallPiece { ); } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; let air = Block::AIR.default_state; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs b/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs index de0fa78fc..b208092bb 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/right_turn.rs @@ -102,9 +102,15 @@ impl StructurePieceBase for RightTurnPiece { } } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs b/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs index 7119b6a8a..cf7cac5d0 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/spiral_staircase.rs @@ -46,7 +46,7 @@ impl Clone for SpiralStaircasePiece { impl SpiralStaircasePiece { /// Matches Start(Random random, int i, int j) pub fn new_start(random: &mut impl RandomImpl, x: i32, z: i32) -> Self { - let orientation = StructurePiece::get_random_horizontal_direction(random); + let orientation = BlockDirection::get_random_horizontal_direction(random); let bounding_box = BlockBox::create_box(x, 64, z, orientation.get_axis(), 5, 11, 5); let mut piece = StrongholdPiece::new(StructurePieceType::StrongholdStart, 0, bounding_box); piece.piece.set_facing(Some(orientation)); @@ -138,9 +138,15 @@ impl StructurePieceBase for SpiralStaircasePiece { ); } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs b/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs index eb8d54ed8..8abcfb648 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/square_room.rs @@ -123,9 +123,15 @@ impl StructurePieceBase for SquareRoomPiece { ); } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; let air = Block::AIR.default_state; diff --git a/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs b/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs index 6c05e8672..93babe49a 100644 --- a/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs +++ b/pumpkin-world/src/generation/structure/structures/stronghold/stairs.rs @@ -90,9 +90,15 @@ impl StructurePieceBase for StairsPiece { ); } - fn place(&mut self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { let randomizer = StoneBrickRandomizer; - let box_limit = self.piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.piece; let inner = &p.piece; diff --git a/pumpkin-world/src/generation/structure/structures/swamp_hut.rs b/pumpkin-world/src/generation/structure/structures/swamp_hut.rs index 68e9e67d7..cbaeb3c89 100644 --- a/pumpkin-world/src/generation/structure/structures/swamp_hut.rs +++ b/pumpkin-world/src/generation/structure/structures/swamp_hut.rs @@ -1,7 +1,11 @@ use std::sync::Arc; use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use pumpkin_util::{ + BlockDirection, + math::{block_box::BlockBox, position::BlockPos}, + random::RandomGenerator, +}; use serde::Deserialize; use crate::{ @@ -12,7 +16,7 @@ use crate::{ piece::StructurePieceType, shiftable_piece::ShiftableStructurePiece, structures::{ - StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, + StructureGenerator, StructureGeneratorContext, StructurePieceBase, StructurePiecesCollector, StructurePosition, }, }, @@ -40,7 +44,7 @@ impl StructureGenerator for SwampHutGenerator { 7, 7, 9, - StructurePiece::get_random_horizontal_direction(&mut context.random).get_axis(), + BlockDirection::get_random_horizontal_direction(&mut context.random).get_axis(), ), })); @@ -61,7 +65,13 @@ impl StructurePieceBase for SwampHutPiece { Box::new(self.clone()) } - fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { if !self .shiftable_structure_piece .adjust_to_average_height(chunk) @@ -69,7 +79,7 @@ impl StructurePieceBase for SwampHutPiece { return; } - let box_limit = self.shiftable_structure_piece.piece.bounding_box; + let box_limit = *chunk_box; let p = &self.shiftable_structure_piece.piece; let spruce_planks = Block::SPRUCE_PLANKS.default_state; diff --git a/pumpkin-world/src/generation/structure/template/template_piece.rs b/pumpkin-world/src/generation/structure/template/template_piece.rs index 26ff8c22a..6b419aa6a 100644 --- a/pumpkin-world/src/generation/structure/template/template_piece.rs +++ b/pumpkin-world/src/generation/structure/template/template_piece.rs @@ -163,7 +163,13 @@ impl StructurePieceBase for TemplatePiece { Box::new(self.clone()) } - fn place(&mut self, chunk: &mut ProtoChunk, _random: &mut RandomGenerator, _seed: i64) { + fn place( + &mut self, + chunk: &mut ProtoChunk, + _random: &mut RandomGenerator, + _seed: i64, + _chunk_box: &BlockBox, + ) { self.place_blocks(chunk); // TODO: Spawn entities from template