1use std::cmp::Ordering;
2
3use glam::IVec3;
4use steel_registry::blocks::block_state_ext::BlockStateExt as _;
5use steel_registry::blocks::properties::BlockStateProperties;
6use steel_registry::{Registry, vanilla_blocks};
7use steel_utils::random::legacy_random::LegacyRandom;
8use steel_utils::random::worldgen_random::WorldgenRandom;
9use steel_utils::random::{PositionalRandom, Random};
10use steel_utils::{BlockPos, BlockStateId, BoundingBox, Direction, types::UpdateFlags};
11
12use super::StructurePiecePlacer;
13use crate::chunk::heightmap::HeightmapType;
14use crate::worldgen::region::WorldGenRegion;
15use crate::worldgen::template::StructureTemplate;
16use steel_worldgen::structure::desert_pyramid::DesertPyramidPieceData;
17use steel_worldgen::structure::{
18 ProceduralPieceData, StructurePiece, StructurePiecePayload, StructureStart,
19};
20
21const DESERT_PYRAMID_LOOT: &str = "minecraft:chests/desert_pyramid";
22const DESERT_PYRAMID_ARCHAEOLOGY: &str = "minecraft:archaeology/desert_pyramid";
23const HORIZONTAL_PLANE: [Direction; 4] = [
24 Direction::North,
25 Direction::East,
26 Direction::South,
27 Direction::West,
28];
29
30impl StructurePiecePlacer {
31 pub(super) fn place_desert_pyramid_piece(
32 region: &mut WorldGenRegion<'_>,
33 registry: &Registry,
34 bounding_box: &mut BoundingBox,
35 orientation: Option<Direction>,
36 data: &mut DesertPyramidPieceData,
37 clip: BoundingBox,
38 random: &mut WorldgenRandom,
39 ) -> bool {
40 let offset = -random.next_i32_bounded(3);
41 let mut placer = DesertPyramidPlacer {
42 region,
43 registry,
44 bounding_box,
45 orientation,
46 clip,
47 data,
48 };
49 if !placer.update_height_position_to_lowest_ground_height(offset) {
50 return false;
51 }
52
53 placer.place(random);
54 true
55 }
56
57 pub(super) fn after_place_desert_pyramid(
58 region: &mut WorldGenRegion<'_>,
59 pieces: &mut [StructurePiece],
60 clip: BoundingBox,
61 ) {
62 let mut unique_sand_placements = Vec::new();
63 for piece in pieces.iter_mut() {
64 let StructurePiecePayload::Procedural(ProceduralPieceData::DesertPyramid(data)) =
65 &mut piece.payload
66 else {
67 continue;
68 };
69 unique_sand_placements.extend(
70 data.potential_suspicious_sand_world_positions
71 .iter()
72 .copied(),
73 );
74 Self::place_desert_pyramid_suspicious_sand(
75 region,
76 clip,
77 data.random_collapsed_roof_pos,
78 );
79 }
80
81 unique_sand_placements.sort_by(Self::compare_vec3i);
82 unique_sand_placements.dedup();
83
84 let Some(structure_box) = StructureStart::compute_bounding_box(pieces, 0) else {
85 return;
86 };
87 let center = structure_box.center();
88 let mut seed_random = LegacyRandom::from_seed(region.seed() as u64);
89 let splitter = seed_random.next_positional();
90 let mut positional_random = splitter.at(center.x, center.y, center.z);
91 Self::shuffle_block_positions(&mut unique_sand_placements, &mut positional_random);
92 let mut suspicious_sand_to_place = unique_sand_placements
93 .len()
94 .min(positional_random.next_i32_between_exclusive(5, 8) as usize);
95
96 for pos in unique_sand_placements {
97 if suspicious_sand_to_place > 0 {
98 suspicious_sand_to_place -= 1;
99 Self::place_desert_pyramid_suspicious_sand(region, clip, pos);
100 } else if clip.contains_blockpos(pos) {
101 let _ = region.set_block_state(
102 pos,
103 vanilla_blocks::SAND.default_state(),
104 UpdateFlags::UPDATE_CLIENTS,
105 );
106 }
107 }
108 }
109
110 fn place_desert_pyramid_suspicious_sand(
111 region: &mut WorldGenRegion<'_>,
112 clip: BoundingBox,
113 pos: BlockPos,
114 ) {
115 if !clip.contains_blockpos(pos) {
116 return;
117 }
118 let state = vanilla_blocks::SUSPICIOUS_SAND.default_state();
119 if region.set_block_state(pos, state, UpdateFlags::UPDATE_CLIENTS) {
120 let _ = Self::set_brushable_loot_table(region, pos, state, DESERT_PYRAMID_ARCHAEOLOGY);
121 }
122 }
123
124 fn compare_vec3i(left: &BlockPos, right: &BlockPos) -> Ordering {
125 left.y()
126 .cmp(&right.y())
127 .then_with(|| left.z().cmp(&right.z()))
128 .then_with(|| left.x().cmp(&right.x()))
129 }
130
131 fn shuffle_block_positions(positions: &mut [BlockPos], random: &mut impl Random) {
132 for i in (1..positions.len()).rev() {
133 let Ok(bound) = i32::try_from(i + 1) else {
134 panic!("block position shuffle length exceeds i32 range");
135 };
136 let j = random.next_i32_bounded(bound) as usize;
137 positions.swap(i, j);
138 }
139 }
140}
141
142struct DesertPyramidPlacer<'a, 'world> {
143 region: &'a mut WorldGenRegion<'world>,
144 registry: &'a Registry,
145 bounding_box: &'a mut BoundingBox,
146 orientation: Option<Direction>,
147 clip: BoundingBox,
148 data: &'a mut DesertPyramidPieceData,
149}
150
151impl DesertPyramidPlacer<'_, '_> {
152 fn update_height_position_to_lowest_ground_height(&mut self, offset: i32) -> bool {
153 if self.data.height_position.is_some() {
154 return true;
155 }
156
157 let mut lowest_ground_height = self.region.max_y_exclusive() + 1;
158 let mut found_position_within_bounding_box = false;
159 for z in self.bounding_box.min_z()..=self.bounding_box.max_z() {
160 for x in self.bounding_box.min_x()..=self.bounding_box.max_x() {
161 lowest_ground_height = lowest_ground_height.min(self.region.height_at(
162 HeightmapType::MotionBlockingNoLeaves,
163 x,
164 z,
165 ));
166 found_position_within_bounding_box = true;
167 }
168 }
169
170 if !found_position_within_bounding_box {
171 return false;
172 }
173
174 self.data.height_position = Some(lowest_ground_height);
175 let dy = lowest_ground_height - self.bounding_box.min_y() + offset;
176 *self.bounding_box = self.bounding_box.translate(IVec3::new(0, dy, 0));
177 true
178 }
179
180 #[expect(
181 clippy::too_many_lines,
182 reason = "desert pyramid placement is a direct port of vanilla's linear postProcess"
183 )]
184 fn place(&mut self, random: &mut WorldgenRandom) {
185 let sandstone = vanilla_blocks::SANDSTONE.default_state();
186 let air = vanilla_blocks::AIR.default_state();
187 let cut_sandstone = vanilla_blocks::CUT_SANDSTONE.default_state();
188 let chiseled_sandstone = vanilla_blocks::CHISELED_SANDSTONE.default_state();
189 let orange_terracotta = vanilla_blocks::ORANGE_TERRACOTTA.default_state();
190 let blue_terracotta = vanilla_blocks::BLUE_TERRACOTTA.default_state();
191 let sand = vanilla_blocks::SAND.default_state();
192
193 self.generate_box(0, -4, 0, 20, 0, 20, sandstone, sandstone, false);
194
195 for pos in 1..=9 {
196 self.generate_box(
197 pos,
198 pos,
199 pos,
200 20 - pos,
201 pos,
202 20 - pos,
203 sandstone,
204 sandstone,
205 false,
206 );
207 self.generate_box(
208 pos + 1,
209 pos,
210 pos + 1,
211 19 - pos,
212 pos,
213 19 - pos,
214 air,
215 air,
216 false,
217 );
218 }
219
220 for x in 0..21 {
221 for z in 0..21 {
222 self.fill_column_down(sandstone, x, -5, z);
223 }
224 }
225
226 let north_stairs = Self::stairs(Direction::North);
227 let south_stairs = Self::stairs(Direction::South);
228 let east_stairs = Self::stairs(Direction::East);
229 let west_stairs = Self::stairs(Direction::West);
230
231 self.generate_box(0, 0, 0, 4, 9, 4, sandstone, air, false);
232 self.generate_box(1, 10, 1, 3, 10, 3, sandstone, sandstone, false);
233 self.place_block(north_stairs, 2, 10, 0);
234 self.place_block(south_stairs, 2, 10, 4);
235 self.place_block(east_stairs, 0, 10, 2);
236 self.place_block(west_stairs, 4, 10, 2);
237 self.generate_box(16, 0, 0, 20, 9, 4, sandstone, air, false);
238 self.generate_box(17, 10, 1, 19, 10, 3, sandstone, sandstone, false);
239 self.place_block(north_stairs, 18, 10, 0);
240 self.place_block(south_stairs, 18, 10, 4);
241 self.place_block(east_stairs, 16, 10, 2);
242 self.place_block(west_stairs, 20, 10, 2);
243 self.generate_box(8, 0, 0, 12, 4, 4, sandstone, air, false);
244 self.generate_box(9, 1, 0, 11, 3, 4, air, air, false);
245 self.place_block(cut_sandstone, 9, 1, 1);
246 self.place_block(cut_sandstone, 9, 2, 1);
247 self.place_block(cut_sandstone, 9, 3, 1);
248 self.place_block(cut_sandstone, 10, 3, 1);
249 self.place_block(cut_sandstone, 11, 3, 1);
250 self.place_block(cut_sandstone, 11, 2, 1);
251 self.place_block(cut_sandstone, 11, 1, 1);
252 self.generate_box(4, 1, 1, 8, 3, 3, sandstone, air, false);
253 self.generate_box(4, 1, 2, 8, 2, 2, air, air, false);
254 self.generate_box(12, 1, 1, 16, 3, 3, sandstone, air, false);
255 self.generate_box(12, 1, 2, 16, 2, 2, air, air, false);
256 self.generate_box(5, 4, 5, 15, 4, 15, sandstone, sandstone, false);
257 self.generate_box(9, 4, 9, 11, 4, 11, air, air, false);
258 self.generate_box(8, 1, 8, 8, 3, 8, cut_sandstone, cut_sandstone, false);
259 self.generate_box(12, 1, 8, 12, 3, 8, cut_sandstone, cut_sandstone, false);
260 self.generate_box(8, 1, 12, 8, 3, 12, cut_sandstone, cut_sandstone, false);
261 self.generate_box(12, 1, 12, 12, 3, 12, cut_sandstone, cut_sandstone, false);
262 self.generate_box(1, 1, 5, 4, 4, 11, sandstone, sandstone, false);
263 self.generate_box(16, 1, 5, 19, 4, 11, sandstone, sandstone, false);
264 self.generate_box(6, 7, 9, 6, 7, 11, sandstone, sandstone, false);
265 self.generate_box(14, 7, 9, 14, 7, 11, sandstone, sandstone, false);
266 self.generate_box(5, 5, 9, 5, 7, 11, cut_sandstone, cut_sandstone, false);
267 self.generate_box(15, 5, 9, 15, 7, 11, cut_sandstone, cut_sandstone, false);
268 self.place_block(air, 5, 5, 10);
269 self.place_block(air, 5, 6, 10);
270 self.place_block(air, 6, 6, 10);
271 self.place_block(air, 15, 5, 10);
272 self.place_block(air, 15, 6, 10);
273 self.place_block(air, 14, 6, 10);
274 self.generate_box(2, 4, 4, 2, 6, 4, air, air, false);
275 self.generate_box(18, 4, 4, 18, 6, 4, air, air, false);
276 self.place_block(north_stairs, 2, 4, 5);
277 self.place_block(north_stairs, 2, 3, 4);
278 self.place_block(north_stairs, 18, 4, 5);
279 self.place_block(north_stairs, 18, 3, 4);
280 self.generate_box(1, 1, 3, 2, 2, 3, sandstone, sandstone, false);
281 self.generate_box(18, 1, 3, 19, 2, 3, sandstone, sandstone, false);
282 self.place_block(sandstone, 1, 1, 2);
283 self.place_block(sandstone, 19, 1, 2);
284 self.place_block(vanilla_blocks::SANDSTONE_SLAB.default_state(), 1, 2, 2);
285 self.place_block(vanilla_blocks::SANDSTONE_SLAB.default_state(), 19, 2, 2);
286 self.place_block(west_stairs, 2, 1, 2);
287 self.place_block(east_stairs, 18, 1, 2);
288 self.generate_box(4, 3, 5, 4, 3, 17, sandstone, sandstone, false);
289 self.generate_box(16, 3, 5, 16, 3, 17, sandstone, sandstone, false);
290 self.generate_box(3, 1, 5, 4, 2, 16, air, air, false);
291 self.generate_box(15, 1, 5, 16, 2, 16, air, air, false);
292
293 for z in (5..=17).step_by(2) {
294 self.place_block(cut_sandstone, 4, 1, z);
295 self.place_block(chiseled_sandstone, 4, 2, z);
296 self.place_block(cut_sandstone, 16, 1, z);
297 self.place_block(chiseled_sandstone, 16, 2, z);
298 }
299
300 self.place_block(orange_terracotta, 10, 0, 7);
301 self.place_block(orange_terracotta, 10, 0, 8);
302 self.place_block(orange_terracotta, 9, 0, 9);
303 self.place_block(orange_terracotta, 11, 0, 9);
304 self.place_block(orange_terracotta, 8, 0, 10);
305 self.place_block(orange_terracotta, 12, 0, 10);
306 self.place_block(orange_terracotta, 7, 0, 10);
307 self.place_block(orange_terracotta, 13, 0, 10);
308 self.place_block(orange_terracotta, 9, 0, 11);
309 self.place_block(orange_terracotta, 11, 0, 11);
310 self.place_block(orange_terracotta, 10, 0, 12);
311 self.place_block(orange_terracotta, 10, 0, 13);
312 self.place_block(blue_terracotta, 10, 0, 10);
313
314 for x in [0, 20] {
315 self.place_block(cut_sandstone, x, 2, 1);
316 self.place_block(orange_terracotta, x, 2, 2);
317 self.place_block(cut_sandstone, x, 2, 3);
318 self.place_block(cut_sandstone, x, 3, 1);
319 self.place_block(orange_terracotta, x, 3, 2);
320 self.place_block(cut_sandstone, x, 3, 3);
321 self.place_block(orange_terracotta, x, 4, 1);
322 self.place_block(chiseled_sandstone, x, 4, 2);
323 self.place_block(orange_terracotta, x, 4, 3);
324 self.place_block(cut_sandstone, x, 5, 1);
325 self.place_block(orange_terracotta, x, 5, 2);
326 self.place_block(cut_sandstone, x, 5, 3);
327 self.place_block(orange_terracotta, x, 6, 1);
328 self.place_block(chiseled_sandstone, x, 6, 2);
329 self.place_block(orange_terracotta, x, 6, 3);
330 self.place_block(orange_terracotta, x, 7, 1);
331 self.place_block(orange_terracotta, x, 7, 2);
332 self.place_block(orange_terracotta, x, 7, 3);
333 self.place_block(cut_sandstone, x, 8, 1);
334 self.place_block(cut_sandstone, x, 8, 2);
335 self.place_block(cut_sandstone, x, 8, 3);
336 }
337
338 for x in [2, 18] {
339 self.place_block(cut_sandstone, x - 1, 2, 0);
340 self.place_block(orange_terracotta, x, 2, 0);
341 self.place_block(cut_sandstone, x + 1, 2, 0);
342 self.place_block(cut_sandstone, x - 1, 3, 0);
343 self.place_block(orange_terracotta, x, 3, 0);
344 self.place_block(cut_sandstone, x + 1, 3, 0);
345 self.place_block(orange_terracotta, x - 1, 4, 0);
346 self.place_block(chiseled_sandstone, x, 4, 0);
347 self.place_block(orange_terracotta, x + 1, 4, 0);
348 self.place_block(cut_sandstone, x - 1, 5, 0);
349 self.place_block(orange_terracotta, x, 5, 0);
350 self.place_block(cut_sandstone, x + 1, 5, 0);
351 self.place_block(orange_terracotta, x - 1, 6, 0);
352 self.place_block(chiseled_sandstone, x, 6, 0);
353 self.place_block(orange_terracotta, x + 1, 6, 0);
354 self.place_block(orange_terracotta, x - 1, 7, 0);
355 self.place_block(orange_terracotta, x, 7, 0);
356 self.place_block(orange_terracotta, x + 1, 7, 0);
357 self.place_block(cut_sandstone, x - 1, 8, 0);
358 self.place_block(cut_sandstone, x, 8, 0);
359 self.place_block(cut_sandstone, x + 1, 8, 0);
360 }
361
362 self.generate_box(8, 4, 0, 12, 6, 0, cut_sandstone, cut_sandstone, false);
363 self.place_block(air, 8, 6, 0);
364 self.place_block(air, 12, 6, 0);
365 self.place_block(orange_terracotta, 9, 5, 0);
366 self.place_block(chiseled_sandstone, 10, 5, 0);
367 self.place_block(orange_terracotta, 11, 5, 0);
368 self.generate_box(8, -14, 8, 12, -11, 12, cut_sandstone, cut_sandstone, false);
369 self.generate_box(
370 8,
371 -10,
372 8,
373 12,
374 -10,
375 12,
376 chiseled_sandstone,
377 chiseled_sandstone,
378 false,
379 );
380 self.generate_box(8, -9, 8, 12, -9, 12, cut_sandstone, cut_sandstone, false);
381 self.generate_box(8, -8, 8, 12, -1, 12, sandstone, sandstone, false);
382 self.generate_box(9, -11, 9, 11, -1, 11, air, air, false);
383 self.place_block(
384 vanilla_blocks::STONE_PRESSURE_PLATE.default_state(),
385 10,
386 -11,
387 10,
388 );
389 self.generate_box(
390 9,
391 -13,
392 9,
393 11,
394 -13,
395 11,
396 vanilla_blocks::TNT.default_state(),
397 air,
398 false,
399 );
400 self.place_block(air, 8, -11, 10);
401 self.place_block(air, 8, -10, 10);
402 self.place_block(chiseled_sandstone, 7, -10, 10);
403 self.place_block(cut_sandstone, 7, -11, 10);
404 self.place_block(air, 12, -11, 10);
405 self.place_block(air, 12, -10, 10);
406 self.place_block(chiseled_sandstone, 13, -10, 10);
407 self.place_block(cut_sandstone, 13, -11, 10);
408 self.place_block(air, 10, -11, 8);
409 self.place_block(air, 10, -10, 8);
410 self.place_block(chiseled_sandstone, 10, -10, 7);
411 self.place_block(cut_sandstone, 10, -11, 7);
412 self.place_block(air, 10, -11, 12);
413 self.place_block(air, 10, -10, 12);
414 self.place_block(chiseled_sandstone, 10, -10, 13);
415 self.place_block(cut_sandstone, 10, -11, 13);
416
417 for direction in HORIZONTAL_PLANE {
418 let chest_index = direction_2d_data_value(direction);
419 if !self.data.has_placed_chest[chest_index] {
420 let (dx, dz) = direction.offset_xz();
421 let chest_pos = self.world_pos(10 + dx * 2, -11, 10 + dz * 2);
422 self.data.has_placed_chest[chest_index] = StructurePiecePlacer::create_loot_chest(
423 self.region,
424 self.clip,
425 random,
426 chest_pos,
427 DESERT_PYRAMID_LOOT,
428 );
429 }
430 }
431
432 self.add_cellar(
433 sand,
434 sandstone,
435 cut_sandstone,
436 chiseled_sandstone,
437 orange_terracotta,
438 blue_terracotta,
439 );
440 }
441
442 fn add_cellar(
443 &mut self,
444 sand: BlockStateId,
445 sandstone: BlockStateId,
446 cut_sandstone: BlockStateId,
447 chiseled_sandstone: BlockStateId,
448 orange_terracotta: BlockStateId,
449 blue_terracotta: BlockStateId,
450 ) {
451 self.add_cellar_stairs(sand, sandstone);
452 self.add_cellar_room(
453 cut_sandstone,
454 chiseled_sandstone,
455 orange_terracotta,
456 blue_terracotta,
457 );
458 }
459
460 fn add_cellar_stairs(&mut self, sand: BlockStateId, sandstone: BlockStateId) {
461 let stairs = Self::stairs(Direction::West);
462 self.place_block(stairs, 13, -1, 17);
463 self.place_block(stairs, 14, -2, 17);
464 self.place_block(stairs, 15, -3, 17);
465 let variant = self.region.random_mut().next_bool();
466 self.place_block(sand, 12, 0, 17);
467 self.place_block(sand, 13, 0, 17);
468 self.place_block(sand, 14, 0, 17);
469 self.place_block(sand, 15, 0, 17);
470 self.place_block(sand, 16, 0, 17);
471 self.place_block(sand, 14, -1, 17);
472 self.place_block(if variant { sand } else { sandstone }, 15, -1, 17);
473 self.place_block(if variant { sandstone } else { sand }, 16, -1, 17);
474 self.place_block(sand, 15, -2, 17);
475 self.place_block(sandstone, 16, -2, 17);
476 self.place_block(sand, 16, -3, 17);
477 }
478
479 fn add_cellar_room(
480 &mut self,
481 cut_sandstone: BlockStateId,
482 chiseled_sandstone: BlockStateId,
483 orange_terracotta: BlockStateId,
484 blue_terracotta: BlockStateId,
485 ) {
486 self.generate_box(13, -3, 10, 13, -3, 15, cut_sandstone, cut_sandstone, true);
487 self.generate_box(19, -3, 10, 19, -3, 15, cut_sandstone, cut_sandstone, true);
488 self.generate_box(13, -3, 10, 19, -3, 11, cut_sandstone, cut_sandstone, true);
489 self.generate_box(13, -3, 16, 19, -3, 16, cut_sandstone, cut_sandstone, true);
490 self.generate_box(
491 13,
492 -2,
493 10,
494 13,
495 -2,
496 15,
497 chiseled_sandstone,
498 chiseled_sandstone,
499 true,
500 );
501 self.generate_box(
502 19,
503 -2,
504 10,
505 19,
506 -2,
507 15,
508 chiseled_sandstone,
509 chiseled_sandstone,
510 true,
511 );
512 self.generate_box(
513 13,
514 -2,
515 10,
516 19,
517 -2,
518 11,
519 chiseled_sandstone,
520 chiseled_sandstone,
521 true,
522 );
523 self.generate_box(
524 13,
525 -2,
526 16,
527 19,
528 -2,
529 16,
530 chiseled_sandstone,
531 chiseled_sandstone,
532 true,
533 );
534 self.generate_box(13, -1, 10, 13, -1, 15, cut_sandstone, cut_sandstone, true);
535 self.generate_box(19, -1, 10, 19, -1, 15, cut_sandstone, cut_sandstone, true);
536 self.generate_box(13, -1, 10, 19, -1, 11, cut_sandstone, cut_sandstone, true);
537 self.generate_box(13, -1, 16, 19, -1, 16, cut_sandstone, cut_sandstone, true);
538 self.place_sand_box(14, -3, 11, 18, -1, 15);
539 self.place_collapsed_roof(14, 0, 11, 18, 15);
540 self.place_block(blue_terracotta, 16, -4, 13);
541 self.place_block(orange_terracotta, 17, -4, 12);
542 self.place_block(orange_terracotta, 17, -4, 14);
543 self.place_block(orange_terracotta, 15, -4, 12);
544 self.place_block(orange_terracotta, 15, -4, 14);
545 self.place_block(orange_terracotta, 18, -4, 13);
546 self.place_block(orange_terracotta, 14, -4, 13);
547 self.place_block(orange_terracotta, 16, -4, 15);
548 self.place_block(orange_terracotta, 16, -4, 11);
549 self.place_block(orange_terracotta, 19, -4, 13);
550 self.place_sand(19, -3, 13);
551 self.place_sand(19, -2, 13);
552 self.place_block(cut_sandstone, 20, -3, 13);
553 self.place_block(chiseled_sandstone, 20, -2, 13);
554 self.place_block(orange_terracotta, 13, -4, 13);
555 self.place_sand(13, -3, 13);
556 self.place_sand(13, -2, 13);
557 self.place_block(cut_sandstone, 12, -3, 13);
558 self.place_block(chiseled_sandstone, 12, -2, 13);
559 self.place_block(orange_terracotta, 16, -4, 16);
560 self.place_sand(16, -3, 16);
561 self.place_sand(16, -2, 16);
562 self.place_block(orange_terracotta, 16, -4, 10);
563 self.place_sand(16, -3, 10);
564 self.place_sand(16, -2, 10);
565 self.place_block(cut_sandstone, 16, -3, 9);
566 self.place_block(chiseled_sandstone, 16, -2, 9);
567 }
568
569 #[expect(
570 clippy::too_many_arguments,
571 reason = "mirrors vanilla StructurePiece.generateBox parameters"
572 )]
573 fn generate_box(
574 &mut self,
575 x0: i32,
576 y0: i32,
577 z0: i32,
578 x1: i32,
579 y1: i32,
580 z1: i32,
581 edge: BlockStateId,
582 fill: BlockStateId,
583 skip_air: bool,
584 ) {
585 for y in y0..=y1 {
586 for x in x0..=x1 {
587 for z in z0..=z1 {
588 if skip_air && self.get_block(x, y, z).is_air() {
589 continue;
590 }
591 let state = if y != y0 && y != y1 && x != x0 && x != x1 && z != z0 && z != z1 {
592 fill
593 } else {
594 edge
595 };
596 self.place_block(state, x, y, z);
597 }
598 }
599 }
600 }
601
602 fn fill_column_down(&mut self, state: BlockStateId, x: i32, start_y: i32, z: i32) {
603 let mut pos = self.world_pos(x, start_y, z);
604 if !self.clip.contains_blockpos(pos) {
605 return;
606 }
607
608 while Self::is_replaceable_by_structures(self.region.block_state(pos))
609 && pos.y() > self.region.min_y() + 1
610 {
611 let _ = self
612 .region
613 .set_block_state(pos, state, UpdateFlags::UPDATE_CLIENTS);
614 pos = pos.below();
615 }
616 }
617
618 fn place_block(&mut self, state: BlockStateId, x: i32, y: i32, z: i32) {
619 let pos = self.world_pos(x, y, z);
620 if !self.clip.contains_blockpos(pos) {
621 return;
622 }
623
624 let state = self.transform_state(state);
625 let _ = self
626 .region
627 .set_block_state(pos, state, UpdateFlags::UPDATE_CLIENTS);
628 if StructurePiecePlacer::needs_structure_shape_postprocessing(state) {
629 self.region.mark_pos_for_postprocessing(pos);
630 }
631 }
632
633 fn get_block(&self, x: i32, y: i32, z: i32) -> BlockStateId {
634 let pos = self.world_pos(x, y, z);
635 if self.clip.contains_blockpos(pos) {
636 self.region.block_state(pos)
637 } else {
638 vanilla_blocks::AIR.default_state()
639 }
640 }
641
642 fn transform_state(&self, state: BlockStateId) -> BlockStateId {
643 let (mirror, rotation) = StructurePiecePlacer::orientation_transform(self.orientation);
644 StructureTemplate::transform_state(self.registry, state, mirror, rotation)
645 }
646
647 fn place_sand(&mut self, x: i32, y: i32, z: i32) {
648 let pos = self.world_pos(x, y, z);
649 self.data
650 .potential_suspicious_sand_world_positions
651 .push(pos);
652 }
653
654 fn place_sand_box(&mut self, x0: i32, y0: i32, z0: i32, x1: i32, y1: i32, z1: i32) {
655 for y in y0..=y1 {
656 for x in x0..=x1 {
657 for z in z0..=z1 {
658 self.place_sand(x, y, z);
659 }
660 }
661 }
662 }
663
664 fn place_collapsed_roof_piece(&mut self, x: i32, y: i32, z: i32) {
665 let state = if self.region.random_mut().next_f32() < 0.33 {
666 vanilla_blocks::SANDSTONE.default_state()
667 } else {
668 vanilla_blocks::SAND.default_state()
669 };
670 self.place_block(state, x, y, z);
671 }
672
673 fn place_collapsed_roof(&mut self, x0: i32, y0: i32, z0: i32, x1: i32, z1: i32) {
674 for x in x0..=x1 {
675 for z in z0..=z1 {
676 self.place_collapsed_roof_piece(x, y0, z);
677 }
678 }
679
680 let seed_pos = self.world_pos(x0, y0, z0);
681 let mut seed_random = LegacyRandom::from_seed(self.region.seed() as u64);
682 let splitter = seed_random.next_positional();
683 let mut positional_random = splitter.at(seed_pos.x(), seed_pos.y(), seed_pos.z());
684 let roof_x = positional_random.next_i32_between(x0, x1);
685 let roof_z = positional_random.next_i32_between(z0, z1);
686 self.data.random_collapsed_roof_pos = self.world_pos(roof_x, y0, roof_z);
687 }
688
689 const fn world_pos(&self, x: i32, y: i32, z: i32) -> BlockPos {
690 let world_y = if self.orientation.is_some() {
691 y + self.bounding_box.min_y()
692 } else {
693 y
694 };
695 let (world_x, world_z) = match self.orientation {
696 None | Some(Direction::Up | Direction::Down) => (x, z),
697 Some(Direction::North) => {
698 (self.bounding_box.min_x() + x, self.bounding_box.max_z() - z)
699 }
700 Some(Direction::South) => {
701 (self.bounding_box.min_x() + x, self.bounding_box.min_z() + z)
702 }
703 Some(Direction::West) => (self.bounding_box.max_x() - z, self.bounding_box.min_z() + x),
704 Some(Direction::East) => (self.bounding_box.min_x() + z, self.bounding_box.min_z() + x),
705 };
706 BlockPos::new(world_x, world_y, world_z)
707 }
708
709 fn is_replaceable_by_structures(state: BlockStateId) -> bool {
710 state.is_air()
711 || state.has_fluid()
712 || state.get_block() == &vanilla_blocks::GLOW_LICHEN
713 || state.get_block() == &vanilla_blocks::SEAGRASS
714 || state.get_block() == &vanilla_blocks::TALL_SEAGRASS
715 }
716
717 fn stairs(facing: Direction) -> BlockStateId {
718 vanilla_blocks::SANDSTONE_STAIRS
719 .default_state()
720 .set_value(&BlockStateProperties::FACING, facing)
721 }
722}
723
724const fn direction_2d_data_value(direction: Direction) -> usize {
725 match direction {
726 Direction::South => 0,
727 Direction::West => 1,
728 Direction::North => 2,
729 Direction::East => 3,
730 Direction::Down | Direction::Up => {
731 panic!("desert pyramid chest directions come from the horizontal plane")
732 }
733 }
734}