steel_core/worldgen/feature/features/
fossil.rs1use 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}