Skip to main content

steel_core/worldgen/feature/features/
fossil.rs

1use std::sync::Arc;
2
3use crate::worldgen::template::{
4    StructurePlaceSettings, StructureProcessorRandom, StructureTemplate,
5};
6use glam::IVec3;
7use steel_utils::{BoundingBox, Rotation};
8use steel_worldgen::structure::{StructureBlockIgnore, StructureMirror};
9
10use super::super::prelude::*;
11use super::super::runner::FeatureDecorationRunner;
12use steel_registry::structure::LiquidSettingsData;
13use steel_registry::structure_processor::StructureProcessorKind;
14
15const VANILLA_ROTATIONS: [Rotation; 4] = [
16    Rotation::None,
17    Rotation::Clockwise90,
18    Rotation::Clockwise180,
19    Rotation::CounterClockwise90,
20];
21
22impl FeatureDecorationRunner {
23    pub(in crate::worldgen::feature) fn place_fossil_feature(
24        region: &mut WorldGenRegion<'_>,
25        registry: &Registry,
26        random: &mut WorldgenRandom,
27        config: &FossilConfiguration,
28        origin: BlockPos,
29    ) -> bool {
30        assert!(
31            !(config.fossil_structures.is_empty() || config.overlay_structures.is_empty()),
32            "fossil feature must have at least one base and overlay structure"
33        );
34        assert!(
35            config.fossil_structures.len() == config.overlay_structures.len(),
36            "fossil feature has {} base structures but {} overlay structures",
37            config.fossil_structures.len(),
38            config.overlay_structures.len()
39        );
40
41        let rotation = VANILLA_ROTATIONS[random.next_i32_bounded(4) as usize];
42        let structure_count = config.fossil_structures.len();
43        let Ok(structure_count_i32) = i32::try_from(structure_count) else {
44            panic!("fossil structure count {structure_count} exceeds i32 range");
45        };
46        let structure_index = random.next_i32_bounded(structure_count_i32) as usize;
47
48        let fossil_template =
49            Self::load_fossil_template(registry, &config.fossil_structures[structure_index]);
50        let overlay_template =
51            Self::load_fossil_template(registry, &config.overlay_structures[structure_index]);
52
53        let size = fossil_template.size(rotation);
54        let low_corner = origin.offset(-size[0] / 2, 0, -size[2] / 2);
55        let lowest_surface_y =
56            Self::lowest_fossil_surface_y(region, low_corner, size[0], size[2], origin.y());
57        let target_y =
58            (lowest_surface_y - 15 - random.next_i32_bounded(10)).max(region.min_y() + 10);
59        let target_pos =
60            fossil_template.zero_position_with_transform(low_corner.at_y(target_y), rotation);
61        let template_box = fossil_template.bounding_box(target_pos, rotation);
62
63        if Self::count_empty_corners(region, template_box) > config.max_empty_corners_allowed {
64            return false;
65        }
66
67        let bounding_box = Self::fossil_bounding_box(region, origin);
68        let fossil_processors =
69            Self::structure_processors(registry, &config.fossil_processors, "fossil processors");
70        let overlay_processors = Self::structure_processors(
71            registry,
72            &config.overlay_processors,
73            "fossil overlay processors",
74        );
75
76        let fossil_settings = StructurePlaceSettings {
77            mirror: StructureMirror::None,
78            rotation,
79            rotation_pivot: BlockPos::ZERO,
80            bounding_box,
81            processors: fossil_processors,
82            block_ignore: StructureBlockIgnore::None,
83            late_block_ignore: StructureBlockIgnore::None,
84            replace_jigsaws: false,
85            projection: None,
86            processor_random: StructureProcessorRandom::Placement,
87            liquid_settings: LiquidSettingsData::ApplyWaterlogging,
88        };
89        fossil_template.place_in_world(
90            region,
91            registry,
92            target_pos,
93            target_pos,
94            &fossil_settings,
95            random,
96            UpdateFlags::UPDATE_NONE,
97        );
98
99        let overlay_settings = StructurePlaceSettings {
100            mirror: StructureMirror::None,
101            rotation,
102            rotation_pivot: BlockPos::ZERO,
103            bounding_box,
104            processors: overlay_processors,
105            block_ignore: StructureBlockIgnore::None,
106            late_block_ignore: StructureBlockIgnore::None,
107            replace_jigsaws: false,
108            projection: None,
109            processor_random: StructureProcessorRandom::Placement,
110            liquid_settings: LiquidSettingsData::ApplyWaterlogging,
111        };
112        overlay_template.place_in_world(
113            region,
114            registry,
115            target_pos,
116            target_pos,
117            &overlay_settings,
118            random,
119            UpdateFlags::UPDATE_NONE,
120        );
121
122        true
123    }
124
125    fn load_fossil_template(registry: &Registry, key: &Identifier) -> Arc<StructureTemplate> {
126        match StructureTemplate::load_vanilla(registry, key) {
127            Ok(template) => template,
128            Err(err) => panic!("{err}"),
129        }
130    }
131
132    fn structure_processors<'a>(
133        registry: &'a Registry,
134        key: &Identifier,
135        context: &str,
136    ) -> &'a [StructureProcessorKind] {
137        let Some(processor_list) = registry.structure_processors.by_key(key) else {
138            panic!("{context} references unknown processor list {key}");
139        };
140        &processor_list.data.processors
141    }
142
143    fn lowest_fossil_surface_y(
144        region: &WorldGenRegion<'_>,
145        low_corner: BlockPos,
146        size_x: i32,
147        size_z: i32,
148        initial_y: i32,
149    ) -> i32 {
150        let mut lowest = initial_y;
151        for dx in 0..size_x {
152            for dz in 0..size_z {
153                let y = region.height_at(
154                    HeightmapType::OceanFloorWg,
155                    low_corner.x() + dx,
156                    low_corner.z() + dz,
157                );
158                lowest = lowest.min(y);
159            }
160        }
161        lowest
162    }
163
164    const fn fossil_bounding_box(region: &WorldGenRegion<'_>, origin: BlockPos) -> BoundingBox {
165        let chunk_x = SectionPos::block_to_section_coord(origin.x());
166        let chunk_z = SectionPos::block_to_section_coord(origin.z());
167        let min_x = chunk_x << 4;
168        let min_z = chunk_z << 4;
169        BoundingBox::new(
170            IVec3::new(min_x - 16, region.min_y(), min_z - 16),
171            IVec3::new(
172                min_x + 15 + 16,
173                region.max_y_exclusive() - 1,
174                min_z + 15 + 16,
175            ),
176        )
177    }
178
179    fn count_empty_corners(region: &WorldGenRegion<'_>, bounding_box: BoundingBox) -> i32 {
180        let mut count = 0;
181        for x in [bounding_box.min_x(), bounding_box.max_x()] {
182            for y in [bounding_box.min_y(), bounding_box.max_y()] {
183                for z in [bounding_box.min_z(), bounding_box.max_z()] {
184                    let state = region.block_state(BlockPos::new(x, y, z));
185                    let block = state.get_block();
186                    if state.is_air()
187                        || block == &vanilla_blocks::LAVA
188                        || block == &vanilla_blocks::WATER
189                    {
190                        count += 1;
191                    }
192                }
193            }
194        }
195        count
196    }
197}