Skip to main content

steel_core/worldgen/feature/features/
fossil.rs

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