steel_worldgen/structure/
igloo.rs1use 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
71pub 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}