Skip to main content

steel_worldgen/structure/
desert_pyramid.rs

1//! Desert pyramid start generation and piece runtime state.
2
3use steel_registry::structure::StructureData;
4use steel_utils::random::legacy_random::LegacyRandom;
5use steel_utils::{BlockPos, Direction, Identifier};
6
7use crate::structure::{
8    GenerationStub, ProceduralPieceData, Structure, StructureGenerationContext, StructurePiece,
9    StructurePiecePayload, make_oriented_piece_bounding_box, random_horizontal_direction,
10};
11
12pub(crate) const DESERT_PYRAMID_WIDTH: i32 = 21;
13pub(crate) const DESERT_PYRAMID_HEIGHT: i32 = 15;
14pub(crate) const DESERT_PYRAMID_DEPTH: i32 = 21;
15
16/// Runtime state for vanilla `DesertPyramidPiece`.
17#[derive(Debug, Clone)]
18pub struct DesertPyramidPieceData {
19    /// Vanilla `ScatteredFeaturePiece.heightPosition`; `None` means not height-adjusted yet.
20    pub height_position: Option<i32>,
21    /// Chest placement flags ordered by `Direction.get2DDataValue`.
22    pub has_placed_chest: [bool; 4],
23    /// Per-run archaeology candidates collected by `postProcess`; vanilla does not persist these.
24    pub potential_suspicious_sand_world_positions: Vec<BlockPos>,
25    /// Per-run collapsed-roof archaeology position collected by `postProcess`.
26    pub random_collapsed_roof_pos: BlockPos,
27}
28
29impl DesertPyramidPieceData {
30    /// Creates the initial runtime state stored on a newly generated desert pyramid piece.
31    #[must_use]
32    pub const fn new() -> Self {
33        Self {
34            height_position: None,
35            has_placed_chest: [false; 4],
36            potential_suspicious_sand_world_positions: Vec::new(),
37            random_collapsed_roof_pos: BlockPos::new(0, 0, 0),
38        }
39    }
40}
41
42impl Default for DesertPyramidPieceData {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48/// Vanilla's `DesertPyramidStructure`.
49pub struct DesertPyramidStructure;
50
51const fn desert_pyramid_piece(
52    chunk_min_x: i32,
53    chunk_min_z: i32,
54    orientation: Direction,
55) -> StructurePiece {
56    StructurePiece {
57        piece_type: Identifier::new_static("minecraft", "tedp"),
58        bounding_box: make_oriented_piece_bounding_box(
59            chunk_min_x,
60            64,
61            chunk_min_z,
62            orientation,
63            DESERT_PYRAMID_WIDTH,
64            DESERT_PYRAMID_HEIGHT,
65            DESERT_PYRAMID_DEPTH,
66        ),
67        gen_depth: 0,
68        orientation: Some(orientation),
69        payload: StructurePiecePayload::Procedural(ProceduralPieceData::DesertPyramid(
70            DesertPyramidPieceData::new(),
71        )),
72        ground_level_delta: 0,
73        junctions: Vec::new(),
74        projection: None,
75    }
76}
77
78impl Structure for DesertPyramidStructure {
79    fn find_generation_point(
80        &self,
81        ctx: &mut dyn StructureGenerationContext,
82        structure: &StructureData,
83        rng: &mut LegacyRandom,
84    ) -> Option<GenerationStub> {
85        let (x0, z0) = (ctx.chunk_min_x(), ctx.chunk_min_z());
86        let h0 = ctx.base_height(x0, z0, false) - 1;
87        let h1 = ctx.base_height(x0, z0 + DESERT_PYRAMID_DEPTH, false) - 1;
88        let h2 = ctx.base_height(x0 + DESERT_PYRAMID_WIDTH, z0, false) - 1;
89        let h3 = ctx.base_height(x0 + DESERT_PYRAMID_WIDTH, z0 + DESERT_PYRAMID_DEPTH, false) - 1;
90        if h0.min(h1).min(h2).min(h3) < ctx.sea_level() {
91            return None;
92        }
93
94        let surface_y = ctx.surface_y();
95        let biome = ctx.biome_at(ctx.center_block_x(), surface_y, ctx.center_block_z());
96        if !structure.allowed_biomes.contains(&biome.key) {
97            return None;
98        }
99
100        let orientation = random_horizontal_direction(rng);
101        Some(GenerationStub {
102            position: (ctx.center_block_x(), surface_y, ctx.center_block_z()),
103            pieces: vec![desert_pyramid_piece(
104                ctx.chunk_min_x(),
105                ctx.chunk_min_z(),
106                orientation,
107            )],
108        })
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use glam::IVec3;
116    use steel_utils::BoundingBox;
117
118    #[test]
119    fn desert_pyramid_piece_uses_full_procedural_payload() {
120        let piece = desert_pyramid_piece(32, -48, Direction::East);
121
122        assert_eq!(
123            piece.piece_type,
124            Identifier::new_static("minecraft", "tedp")
125        );
126        assert_eq!(piece.gen_depth, 0);
127        assert_eq!(piece.orientation, Some(Direction::East));
128        assert_eq!(
129            piece.bounding_box,
130            BoundingBox::new(IVec3::new(32, 64, -48), IVec3::new(52, 78, -28))
131        );
132        let StructurePiecePayload::Procedural(ProceduralPieceData::DesertPyramid(data)) =
133            piece.payload
134        else {
135            panic!("desert pyramid should use its procedural payload");
136        };
137        assert_eq!(data.height_position, None);
138        assert_eq!(data.has_placed_chest, [false; 4]);
139        assert!(data.potential_suspicious_sand_world_positions.is_empty());
140        assert_eq!(data.random_collapsed_roof_pos, BlockPos::new(0, 0, 0));
141    }
142}