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