steel_worldgen/structure/
nether_fossil.rs1use glam::IVec3;
6use steel_registry::structure::{
7 HeightProviderData, LiquidSettingsData, StructureConfigData, StructureData, VerticalAnchorData,
8};
9use steel_utils::Direction;
10use steel_utils::Identifier;
11use steel_utils::Rotation;
12use steel_utils::random::Random;
13use steel_utils::random::legacy_random::LegacyRandom;
14
15use crate::structure::{
16 GenerationStub, Structure, StructureBlockIgnore, StructureGenerationContext, StructureMirror,
17 StructurePiece, StructurePiecePayload, TemplateMarkerHandling, TemplatePieceData,
18 TemplatePlacementAdjustment, TemplatePlacementClip, TemplatePostProcess, TemplateProcessorList,
19};
20
21pub const FOSSIL_COUNT: i32 = 14;
23const SEA_LEVEL: i32 = 32;
24
25pub struct FossilResult {
27 pub template_name: String,
29 pub position: IVec3,
31 pub rotation: Rotation,
33 pub biome_check_pos: IVec3,
35}
36
37const fn resolve_vertical_anchor(
38 anchor: &VerticalAnchorData,
39 min_gen_y: i32,
40 gen_depth: i32,
41) -> i32 {
42 match anchor {
43 VerticalAnchorData::Absolute(y) => *y,
44 VerticalAnchorData::AboveBottom(offset) => min_gen_y + *offset,
45 VerticalAnchorData::BelowTop(offset) => min_gen_y + gen_depth - 1 - *offset,
46 }
47}
48
49fn sample_height(
50 height: &HeightProviderData,
51 rng: &mut LegacyRandom,
52 min_gen_y: i32,
53 gen_depth: i32,
54) -> i32 {
55 match height {
56 HeightProviderData::Constant(anchor) => {
57 resolve_vertical_anchor(anchor, min_gen_y, gen_depth)
58 }
59 HeightProviderData::Uniform {
60 min_inclusive,
61 max_inclusive,
62 } => {
63 let min = resolve_vertical_anchor(min_inclusive, min_gen_y, gen_depth);
64 let max = resolve_vertical_anchor(max_inclusive, min_gen_y, gen_depth);
65 if min > max {
66 min
67 } else {
68 min + rng.next_i32_bounded(max - min + 1)
69 }
70 }
71 }
72}
73
74pub fn find_generation_point<F>(
76 rng: &mut LegacyRandom,
77 chunk_x: i32,
78 chunk_z: i32,
79 height: &HeightProviderData,
80 min_gen_y: i32,
81 gen_depth: i32,
82 mut solid_block_below_air: F,
83) -> Option<FossilResult>
84where
85 F: FnMut(i32, i32, i32, i32) -> Option<i32>,
86{
87 let block_x = (chunk_x << 4) + rng.next_i32_bounded(16);
88 let block_z = (chunk_z << 4) + rng.next_i32_bounded(16);
89
90 let start_y = sample_height(height, rng, min_gen_y, gen_depth);
91 let y = solid_block_below_air(block_x, block_z, start_y, SEA_LEVEL + 1)?;
92
93 let rotation = Rotation::get_random(rng);
94 let fossil_idx = rng.next_i32_bounded(FOSSIL_COUNT) + 1;
95 Some(FossilResult {
96 template_name: format!("nether_fossils/fossil_{fossil_idx}"),
97 position: IVec3::new(block_x, y, block_z),
98 rotation,
99 biome_check_pos: IVec3::new(block_x, y, block_z),
100 })
101}
102
103fn make_nether_fossil_piece(
104 template_id: Identifier,
105 position: IVec3,
106 rotation: Rotation,
107 size: IVec3,
108) -> StructurePiece {
109 StructurePiece {
110 piece_type: Identifier::new_static("minecraft", "nefos"),
111 bounding_box: rotation.get_bounding_box(position, size),
112 gen_depth: 0,
113 orientation: Some(Direction::North),
114 payload: StructurePiecePayload::Template(TemplatePieceData {
115 template_id,
116 template_position: position,
117 rotation,
118 mirror: StructureMirror::None,
119 rotation_pivot: IVec3::ZERO,
120 block_ignore: StructureBlockIgnore::StructureAndAir,
121 late_block_ignore: StructureBlockIgnore::None,
122 processors: TemplateProcessorList::Empty,
123 liquid_settings: LiquidSettingsData::ApplyWaterlogging,
124 marker_handling: TemplateMarkerHandling::Ignore,
125 placement_adjustment: TemplatePlacementAdjustment::None,
126 placement_clip: TemplatePlacementClip::CenterChunkExpandedToTemplate,
127 post_process: TemplatePostProcess::NetherFossil,
128 }),
129 ground_level_delta: 0,
130 junctions: Vec::new(),
131 projection: None,
132 }
133}
134
135pub struct NetherFossilStructure;
137
138impl Structure for NetherFossilStructure {
139 fn find_generation_point(
140 &self,
141 ctx: &mut dyn StructureGenerationContext,
142 structure: &StructureData,
143 rng: &mut LegacyRandom,
144 ) -> Option<GenerationStub> {
145 let min_gen_y = ctx.min_y();
146 let gen_depth = ctx.height();
147 let (chunk_x, chunk_z) = (ctx.chunk_x(), ctx.chunk_z());
148 let StructureConfigData::NetherFossil { height } = &structure.config else {
149 return None;
150 };
151
152 let result = find_generation_point(
153 rng,
154 chunk_x,
155 chunk_z,
156 height,
157 min_gen_y,
158 gen_depth,
159 |x, z, start_y, min_solid_y| ctx.solid_block_below_air(x, z, start_y, min_solid_y),
160 )?;
161
162 let biome = ctx.biome_at(
163 result.biome_check_pos.x,
164 result.biome_check_pos.y,
165 result.biome_check_pos.z,
166 );
167 if !structure.allowed_biomes.contains(&biome.key) {
168 return None;
169 }
170
171 let template_id = Identifier::vanilla(result.template_name);
172 let tmpl_size = ctx.templates().get(&template_id)?.size;
173 Some(GenerationStub {
174 position: (result.position.x, result.position.y, result.position.z),
175 pieces: vec![make_nether_fossil_piece(
176 template_id,
177 result.position,
178 result.rotation,
179 IVec3::from(tmpl_size),
180 )],
181 })
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn nether_fossil_piece_uses_full_template_payload() {
191 let position = IVec3::new(10, 45, -20);
192 let size = IVec3::new(7, 5, 9);
193 let piece = make_nether_fossil_piece(
194 Identifier::vanilla_static("nether_fossils/fossil_1"),
195 position,
196 Rotation::Clockwise90,
197 size,
198 );
199
200 assert_eq!(
201 piece.piece_type,
202 Identifier::new_static("minecraft", "nefos")
203 );
204 assert_eq!(piece.gen_depth, 0);
205 assert_eq!(piece.orientation, Some(Direction::North));
206 assert_eq!(
207 piece.bounding_box,
208 Rotation::Clockwise90.get_bounding_box(position, size),
209 );
210
211 let StructurePiecePayload::Template(data) = piece.payload else {
212 panic!("nether fossil piece should be template-backed");
213 };
214 assert_eq!(
215 data.template_id,
216 Identifier::vanilla_static("nether_fossils/fossil_1")
217 );
218 assert_eq!(data.template_position, position);
219 assert_eq!(data.rotation, Rotation::Clockwise90);
220 assert_eq!(data.mirror, StructureMirror::None);
221 assert_eq!(data.rotation_pivot, IVec3::ZERO);
222 assert_eq!(data.block_ignore, StructureBlockIgnore::StructureAndAir);
223 assert_eq!(data.late_block_ignore, StructureBlockIgnore::None);
224 assert_eq!(data.processors, TemplateProcessorList::Empty);
225 assert_eq!(data.liquid_settings, LiquidSettingsData::ApplyWaterlogging);
226 assert_eq!(data.marker_handling, TemplateMarkerHandling::Ignore);
227 assert_eq!(data.placement_adjustment, TemplatePlacementAdjustment::None);
228 assert_eq!(
229 data.placement_clip,
230 TemplatePlacementClip::CenterChunkExpandedToTemplate
231 );
232 assert_eq!(data.post_process, TemplatePostProcess::NetherFossil);
233 }
234}