1mod buried_treasure;
9mod desert_pyramid;
10mod fortress;
11mod jungle_temple;
12mod mineshaft;
13mod ocean_monument;
14mod pool_element;
15mod ruined_portal;
16mod scattered_feature;
17mod stronghold;
18mod swamp_hut;
19mod template_piece;
20mod template_processors;
21
22use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
23use steel_registry::block_entity_type::BlockEntityTypeRef;
24use steel_registry::blocks::block_state_ext::BlockStateExt as _;
25use steel_registry::blocks::properties::BlockStateProperties;
26use steel_registry::structure::StructureRef;
27use steel_registry::{Registry, vanilla_block_entity_types, vanilla_blocks};
28use steel_utils::random::Random;
29use steel_utils::random::worldgen_random::WorldgenRandom;
30use steel_utils::{
31 BlockPos, BlockStateId, BoundingBox, Direction, Identifier, PackedBlockPos, Rotation,
32 types::UpdateFlags,
33};
34
35use crate::worldgen::region::WorldGenRegion;
36use steel_worldgen::structure::{
37 ProceduralPieceData, StructureMirror, StructurePiece, StructurePiecePayload,
38};
39
40pub(crate) struct StructurePiecePlacer;
41
42impl StructurePiecePlacer {
43 pub(crate) const JIGSAW_UPDATE_FLAGS: UpdateFlags =
45 UpdateFlags::UPDATE_CLIENTS.union(UpdateFlags::UPDATE_KNOWN_SHAPE);
46 pub(crate) const TEMPLATE_UPDATE_FLAGS: UpdateFlags = UpdateFlags::UPDATE_CLIENTS;
48
49 #[expect(
55 clippy::too_many_lines,
56 reason = "single dispatch boundary for all structure piece payload families"
57 )]
58 pub(crate) fn place_piece(
59 region: &mut WorldGenRegion<'_>,
60 registry: &Registry,
61 piece: &mut StructurePiece,
62 reference_pos: BlockPos,
63 clip: BoundingBox,
64 random: &mut WorldgenRandom,
65 biome_zoom_seed: i64,
66 ) -> bool {
67 let mut piece_bounding_box = piece.bounding_box;
68 let piece_orientation = piece.orientation;
69 let placed = match &mut piece.payload {
70 StructurePiecePayload::Jigsaw(data) => Self::place_pool_element(
71 region,
72 registry,
73 &data.pool_element,
74 BlockPos::new(data.position.x, data.position.y, data.position.z),
75 reference_pos,
76 data.rotation,
77 clip,
78 random,
79 data.liquid_settings,
80 biome_zoom_seed,
81 ),
82 StructurePiecePayload::Template(data) => Self::place_template_piece(
83 region,
84 registry,
85 data,
86 &mut piece_bounding_box,
87 reference_pos,
88 clip,
89 random,
90 ),
91 StructurePiecePayload::Procedural(ProceduralPieceData::Mineshaft(data)) => {
92 Self::place_mineshaft_piece(
93 region,
94 registry,
95 piece_bounding_box,
96 piece_orientation,
97 data,
98 clip,
99 random,
100 biome_zoom_seed,
101 )
102 }
103 StructurePiecePayload::Procedural(ProceduralPieceData::NetherFortress(data)) => {
104 Self::place_nether_fortress_piece(
105 region,
106 registry,
107 piece_bounding_box,
108 piece_orientation,
109 data,
110 clip,
111 random,
112 )
113 }
114 StructurePiecePayload::Procedural(ProceduralPieceData::OceanMonument(data)) => {
115 Self::place_ocean_monument_piece(
116 region,
117 registry,
118 piece_bounding_box,
119 piece_orientation,
120 data,
121 clip,
122 random,
123 )
124 }
125 StructurePiecePayload::Procedural(ProceduralPieceData::Stronghold(data)) => {
126 Self::place_stronghold_piece(
127 region,
128 registry,
129 piece_bounding_box,
130 piece_orientation,
131 data,
132 clip,
133 random,
134 )
135 }
136 StructurePiecePayload::Procedural(ProceduralPieceData::BuriedTreasure) => {
137 Self::place_buried_treasure_piece(region, &mut piece_bounding_box, clip, random)
138 }
139 StructurePiecePayload::Procedural(ProceduralPieceData::DesertPyramid(data)) => {
140 Self::place_desert_pyramid_piece(
141 region,
142 registry,
143 &mut piece_bounding_box,
144 piece_orientation,
145 data,
146 clip,
147 random,
148 )
149 }
150 StructurePiecePayload::Procedural(ProceduralPieceData::JungleTemple(data)) => {
151 Self::place_jungle_temple_piece(
152 region,
153 registry,
154 &mut piece_bounding_box,
155 piece_orientation,
156 data,
157 clip,
158 random,
159 )
160 }
161 StructurePiecePayload::Procedural(ProceduralPieceData::SwampHut(data)) => {
162 Self::place_swamp_hut_piece(
163 region,
164 registry,
165 &mut piece_bounding_box,
166 piece_orientation,
167 data,
168 clip,
169 random,
170 )
171 }
172 StructurePiecePayload::Procedural(ProceduralPieceData::Unimplemented) => false,
173 };
174 piece.bounding_box = piece_bounding_box;
175 placed
176 }
177
178 pub(crate) fn after_place_structure(
179 region: &mut WorldGenRegion<'_>,
180 structure: StructureRef,
181 pieces: &mut [StructurePiece],
182 clip: BoundingBox,
183 ) {
184 if structure.structure_type == Identifier::new_static("minecraft", "desert_pyramid") {
185 Self::after_place_desert_pyramid(region, pieces, clip);
186 }
187 }
188
189 const VANILLA_HORIZONTAL_DIRECTIONS: [Direction; 4] = [
190 Direction::North,
191 Direction::East,
192 Direction::South,
193 Direction::West,
194 ];
195
196 pub(super) fn reorient_chest(
197 region: &WorldGenRegion<'_>,
198 pos: BlockPos,
199 state: BlockStateId,
200 ) -> BlockStateId {
201 let mut solid_neighbor = None;
202
203 for direction in Self::VANILLA_HORIZONTAL_DIRECTIONS {
204 let relative_pos = pos.relative(direction);
205 let neighbor = region.block_state(relative_pos);
206 if neighbor.get_block() == &vanilla_blocks::CHEST {
207 return state;
208 }
209
210 if neighbor.is_solid_render() {
211 if solid_neighbor.is_some() {
212 solid_neighbor = None;
213 break;
214 }
215 solid_neighbor = Some(direction);
216 }
217 }
218
219 if let Some(direction) = solid_neighbor {
220 return state.set_value(
221 &BlockStateProperties::HORIZONTAL_FACING,
222 direction.opposite(),
223 );
224 }
225
226 let mut lock_dir = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
227 let mut relative_pos = pos.relative(lock_dir);
228 if region.block_state(relative_pos).is_solid_render() {
229 lock_dir = lock_dir.opposite();
230 relative_pos = pos.relative(lock_dir);
231 }
232 if region.block_state(relative_pos).is_solid_render() {
233 lock_dir = lock_dir.rotate_y_clockwise();
234 relative_pos = pos.relative(lock_dir);
235 }
236 if region.block_state(relative_pos).is_solid_render() {
237 lock_dir = lock_dir.opposite();
238 }
239 state.set_value(&BlockStateProperties::HORIZONTAL_FACING, lock_dir)
240 }
241
242 pub(super) fn create_loot_chest(
243 region: &mut WorldGenRegion<'_>,
244 clip: BoundingBox,
245 random: &mut WorldgenRandom,
246 pos: BlockPos,
247 loot_table: &'static str,
248 ) -> bool {
249 if !clip.contains_blockpos(pos)
250 || region.block_state(pos).get_block() == &vanilla_blocks::CHEST
251 {
252 return false;
253 }
254
255 let state = Self::reorient_chest(region, pos, vanilla_blocks::CHEST.default_state());
256 if !region.set_block_state(pos, state, UpdateFlags::UPDATE_CLIENTS) {
257 return false;
258 }
259
260 Self::set_loot_table_block_entity(
261 region,
262 pos,
263 &vanilla_block_entity_types::CHEST,
264 state,
265 loot_table,
266 random.next_i64(),
267 )
268 }
269
270 pub(super) fn set_loot_table_block_entity(
271 region: &WorldGenRegion<'_>,
272 pos: BlockPos,
273 block_entity_type: BlockEntityTypeRef,
274 state: BlockStateId,
275 loot_table: &'static str,
276 seed: i64,
277 ) -> bool {
278 let mut nbt = NbtCompound::new();
279 nbt.insert("LootTable", loot_table);
280 if seed != 0 {
281 nbt.insert("LootTableSeed", seed);
282 }
283 region.set_block_entity_data(pos, block_entity_type, state, nbt)
284 }
285
286 pub(super) fn set_spawner_entity(
287 region: &WorldGenRegion<'_>,
288 pos: BlockPos,
289 state: BlockStateId,
290 entity_id: &'static str,
291 ) -> bool {
292 let mut entity = NbtCompound::new();
293 entity.insert("id", entity_id);
294
295 let mut spawn_data = NbtCompound::new();
296 spawn_data.insert("entity", NbtTag::Compound(entity));
297
298 let mut nbt = NbtCompound::new();
299 nbt.insert("Delay", 20_i16);
300 nbt.insert("MinSpawnDelay", 200_i16);
301 nbt.insert("MaxSpawnDelay", 800_i16);
302 nbt.insert("SpawnCount", 4_i16);
303 nbt.insert("MaxNearbyEntities", 6_i16);
304 nbt.insert("RequiredPlayerRange", 16_i16);
305 nbt.insert("SpawnRange", 4_i16);
306 nbt.insert("SpawnData", NbtTag::Compound(spawn_data));
307 nbt.insert(
308 "SpawnPotentials",
309 NbtTag::List(NbtList::Compound(Vec::new())),
310 );
311
312 region.set_block_entity_data(pos, &vanilla_block_entity_types::MOB_SPAWNER, state, nbt)
313 }
314
315 pub(super) fn set_brushable_loot_table(
316 region: &WorldGenRegion<'_>,
317 pos: BlockPos,
318 state: BlockStateId,
319 loot_table: &'static str,
320 ) -> bool {
321 Self::set_loot_table_block_entity(
322 region,
323 pos,
324 &vanilla_block_entity_types::BRUSHABLE_BLOCK,
325 state,
326 loot_table,
327 PackedBlockPos::from(pos).as_raw(),
328 )
329 }
330
331 pub(super) const fn orientation_transform(
332 orientation: Option<Direction>,
333 ) -> (StructureMirror, Rotation) {
334 match orientation {
335 None | Some(Direction::North | Direction::Up | Direction::Down) => {
336 (StructureMirror::None, Rotation::None)
337 }
338 Some(Direction::South) => (StructureMirror::LeftRight, Rotation::None),
339 Some(Direction::West) => (StructureMirror::LeftRight, Rotation::Clockwise90),
340 Some(Direction::East) => (StructureMirror::None, Rotation::Clockwise90),
341 }
342 }
343
344 pub(super) fn needs_structure_shape_postprocessing(state: BlockStateId) -> bool {
345 let block = state.get_block();
346 block == &vanilla_blocks::NETHER_BRICK_FENCE
347 || block == &vanilla_blocks::TORCH
348 || block == &vanilla_blocks::WALL_TORCH
349 || block == &vanilla_blocks::OAK_FENCE
350 || block == &vanilla_blocks::SPRUCE_FENCE
351 || block == &vanilla_blocks::DARK_OAK_FENCE
352 || block == &vanilla_blocks::PALE_OAK_FENCE
353 || block == &vanilla_blocks::ACACIA_FENCE
354 || block == &vanilla_blocks::BIRCH_FENCE
355 || block == &vanilla_blocks::JUNGLE_FENCE
356 || block == &vanilla_blocks::LADDER
357 || block == &vanilla_blocks::IRON_BARS
358 }
359}