Skip to main content

steel_core/worldgen/structure/piece_placer/
desert_pyramid.rs

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}