Skip to main content

steel_worldgen/structure/
igloo.rs

1//! Igloo: one top piece always, 50% chance of a basement (laboratory + `depth-1`
2//! ladder segments, depth ∈ [4, 11]).
3
4use glam::IVec3;
5use steel_registry::structure::{LiquidSettingsData, StructureData};
6use steel_utils::random::Random;
7use steel_utils::random::legacy_random::LegacyRandom;
8use steel_utils::{Direction, Identifier, Rotation};
9
10use crate::structure::{
11    GenerationStub, Structure, StructureBlockIgnore, StructureGenerationContext, StructureMirror,
12    StructurePiece, StructurePiecePayload, TemplateMarkerHandling, TemplatePieceData,
13    TemplatePlacementAdjustment, TemplatePlacementClip, TemplatePostProcess, TemplateProcessorList,
14};
15
16const TOP_SIZE: IVec3 = IVec3::new(7, 5, 8);
17const MID_SIZE: IVec3 = IVec3::new(3, 3, 3);
18const BOT_SIZE: IVec3 = IVec3::new(7, 6, 9);
19const TOP_PIVOT: IVec3 = IVec3::new(3, 5, 5);
20const MID_PIVOT: IVec3 = IVec3::new(1, 3, 1);
21const BOT_PIVOT: IVec3 = IVec3::new(3, 6, 7);
22const TOP_OFF: IVec3 = IVec3::new(0, 0, 0);
23const MID_OFF: IVec3 = IVec3::new(2, -3, 4);
24const BOT_OFF: IVec3 = IVec3::new(0, -3, -2);
25const GEN_Y: i32 = 90;
26
27#[expect(
28    clippy::too_many_arguments,
29    reason = "igloo piece construction mirrors vanilla template-piece constants"
30)]
31fn make_igloo_piece(
32    template_path: &'static str,
33    start_x: i32,
34    start_z: i32,
35    rotation: Rotation,
36    off: IVec3,
37    depth: i32,
38    size: IVec3,
39    pivot: IVec3,
40    post_process: TemplatePostProcess,
41) -> StructurePiece {
42    let template_position = IVec3::new(start_x + off.x, GEN_Y + off.y - depth, start_z + off.z);
43    StructurePiece {
44        piece_type: Identifier::new_static("minecraft", "iglu"),
45        bounding_box: rotation.get_bounding_box_with_pivot(template_position, size, pivot),
46        gen_depth: 0,
47        orientation: Some(Direction::North),
48        payload: StructurePiecePayload::Template(TemplatePieceData {
49            template_id: Identifier::vanilla_static(template_path),
50            template_position,
51            rotation,
52            mirror: StructureMirror::None,
53            rotation_pivot: pivot,
54            block_ignore: StructureBlockIgnore::StructureBlock,
55            late_block_ignore: StructureBlockIgnore::None,
56            processors: TemplateProcessorList::Empty,
57            liquid_settings: LiquidSettingsData::IgnoreWaterlogging,
58            marker_handling: TemplateMarkerHandling::Igloo,
59            placement_adjustment: TemplatePlacementAdjustment::Igloo {
60                template_offset: (off.x, off.y, off.z),
61            },
62            placement_clip: TemplatePlacementClip::CenterChunk,
63            post_process,
64        }),
65        ground_level_delta: 0,
66        junctions: Vec::new(),
67        projection: None,
68    }
69}
70
71/// Registered under `"minecraft:igloo"`.
72pub struct IglooStructure;
73
74impl Structure for IglooStructure {
75    fn find_generation_point(
76        &self,
77        ctx: &mut dyn StructureGenerationContext,
78        structure: &StructureData,
79        rng: &mut LegacyRandom,
80    ) -> Option<GenerationStub> {
81        let surface_y = ctx.surface_y();
82        let biome = ctx.biome_at(ctx.center_block_x(), surface_y, ctx.center_block_z());
83        if !structure.allowed_biomes.contains(&biome.key) {
84            return None;
85        }
86
87        let rotation = Rotation::get_random(rng);
88        let (start_x, start_z) = (ctx.chunk_min_x(), ctx.chunk_min_z());
89
90        let mut pieces = Vec::new();
91        if rng.next_f64() < 0.5_f64 {
92            let depth = rng.next_i32_bounded(8) + 4;
93            pieces.push(make_igloo_piece(
94                "igloo/bottom",
95                start_x,
96                start_z,
97                rotation,
98                BOT_OFF,
99                depth * 3,
100                BOT_SIZE,
101                BOT_PIVOT,
102                TemplatePostProcess::None,
103            ));
104            for i in 0..depth - 1 {
105                pieces.push(make_igloo_piece(
106                    "igloo/middle",
107                    start_x,
108                    start_z,
109                    rotation,
110                    MID_OFF,
111                    i * 3,
112                    MID_SIZE,
113                    MID_PIVOT,
114                    TemplatePostProcess::None,
115                ));
116            }
117        }
118        pieces.push(make_igloo_piece(
119            "igloo/top",
120            start_x,
121            start_z,
122            rotation,
123            TOP_OFF,
124            0,
125            TOP_SIZE,
126            TOP_PIVOT,
127            TemplatePostProcess::IglooTop,
128        ));
129
130        Some(GenerationStub {
131            position: (ctx.center_block_x(), surface_y, ctx.center_block_z()),
132            pieces,
133        })
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn igloo_piece_uses_template_payload_with_height_adjustment() {
143        let piece = make_igloo_piece(
144            "igloo/top",
145            32,
146            -48,
147            Rotation::Clockwise90,
148            TOP_OFF,
149            0,
150            TOP_SIZE,
151            TOP_PIVOT,
152            TemplatePostProcess::IglooTop,
153        );
154
155        assert_eq!(
156            piece.piece_type,
157            Identifier::new_static("minecraft", "iglu")
158        );
159        assert_eq!(piece.gen_depth, 0);
160        assert_eq!(piece.orientation, Some(Direction::North));
161        assert_eq!(
162            piece.bounding_box,
163            Rotation::Clockwise90.get_bounding_box_with_pivot(
164                IVec3::new(32, GEN_Y, -48),
165                TOP_SIZE,
166                TOP_PIVOT,
167            ),
168        );
169
170        let StructurePiecePayload::Template(data) = piece.payload else {
171            panic!("igloo piece should be template-backed");
172        };
173        assert_eq!(data.template_id, Identifier::vanilla_static("igloo/top"));
174        assert_eq!(data.template_position, (32, GEN_Y, -48).into());
175        assert_eq!(data.rotation, Rotation::Clockwise90);
176        assert_eq!(data.mirror, StructureMirror::None);
177        assert_eq!(data.rotation_pivot, TOP_PIVOT);
178        assert_eq!(data.block_ignore, StructureBlockIgnore::StructureBlock);
179        assert_eq!(data.late_block_ignore, StructureBlockIgnore::None);
180        assert_eq!(data.processors, TemplateProcessorList::Empty);
181        assert_eq!(data.liquid_settings, LiquidSettingsData::IgnoreWaterlogging);
182        assert_eq!(data.marker_handling, TemplateMarkerHandling::Igloo);
183        assert_eq!(
184            data.placement_adjustment,
185            TemplatePlacementAdjustment::Igloo {
186                template_offset: (TOP_OFF.x, TOP_OFF.y, TOP_OFF.z),
187            }
188        );
189        assert_eq!(data.placement_clip, TemplatePlacementClip::CenterChunk);
190        assert_eq!(data.post_process, TemplatePostProcess::IglooTop);
191    }
192}