1#![expect(
2 clippy::too_many_lines,
3 reason = "placed feature modifier chaining mirrors vanilla placement flow"
4)]
5
6use super::prelude::*;
7use super::runner::FeatureDecorationRunner;
8
9#[derive(Clone, Copy)]
10enum BiomeFilterMode<'a> {
11 Check(Option<&'a Identifier>),
12 Ignore,
13}
14
15impl FeatureDecorationRunner {
16 pub(super) fn place_placed_feature_entry(
17 region: &mut WorldGenRegion<'_>,
18 registry: &Registry,
19 random: &mut WorldgenRandom,
20 origin: BlockPos,
21 feature: PlacedFeatureEntryRef,
22 biome_zoom_seed: i64,
23 ) -> bool {
24 assert!(
25 feature.try_id().is_some(),
26 "top-level placed feature {} is not registered",
27 feature.key
28 );
29 Self::place_placed_feature_data(
30 region,
31 registry,
32 random,
33 origin,
34 &feature.data,
35 Some(&feature.key),
36 biome_zoom_seed,
37 )
38 }
39
40 pub(super) fn place_placed_feature_data(
41 region: &mut WorldGenRegion<'_>,
42 registry: &Registry,
43 random: &mut WorldgenRandom,
44 origin: BlockPos,
45 feature: &PlacedFeatureData,
46 biome_filter: Option<&Identifier>,
47 biome_zoom_seed: i64,
48 ) -> bool {
49 Self::place_placed_feature_from_modifier(
50 region,
51 registry,
52 random,
53 origin,
54 feature,
55 BiomeFilterMode::Check(biome_filter),
56 biome_zoom_seed,
57 0,
58 )
59 }
60
61 #[expect(
62 clippy::too_many_arguments,
63 reason = "threading vanilla placed-feature stream state explicitly"
64 )]
65 fn place_placed_feature_from_modifier(
66 region: &mut WorldGenRegion<'_>,
67 registry: &Registry,
68 random: &mut WorldgenRandom,
69 origin: BlockPos,
70 feature: &PlacedFeatureData,
71 biome_filter: BiomeFilterMode<'_>,
72 biome_zoom_seed: i64,
73 modifier_index: usize,
74 ) -> bool {
75 let Some(modifier) = feature.placement.get(modifier_index) else {
76 return Self::place_configured_feature(
77 region,
78 registry,
79 random,
80 &feature.feature,
81 origin,
82 biome_zoom_seed,
83 );
84 };
85
86 let mut placed = false;
87
88 match modifier {
89 PlacementModifier::Biome => {
90 let biome_allows = match biome_filter {
91 BiomeFilterMode::Check(feature_key) => Self::biome_allows_feature(
92 region,
93 registry,
94 biome_zoom_seed,
95 origin,
96 feature_key,
97 ),
98 BiomeFilterMode::Ignore => true,
99 };
100
101 if biome_allows {
102 placed = Self::place_placed_feature_from_modifier(
103 region,
104 registry,
105 random,
106 origin,
107 feature,
108 biome_filter,
109 biome_zoom_seed,
110 modifier_index + 1,
111 );
112 }
113 }
114 PlacementModifier::BlockPredicateFilter { predicate } => {
115 if Self::test_block_predicate(region, registry, predicate, origin) {
116 placed = Self::place_placed_feature_from_modifier(
117 region,
118 registry,
119 random,
120 origin,
121 feature,
122 biome_filter,
123 biome_zoom_seed,
124 modifier_index + 1,
125 );
126 }
127 }
128 PlacementModifier::Count { count } => {
129 if let Ok(count) = usize::try_from(count.sample(random)) {
130 for _ in 0..count {
131 if Self::place_placed_feature_from_modifier(
132 region,
133 registry,
134 random,
135 origin,
136 feature,
137 biome_filter,
138 biome_zoom_seed,
139 modifier_index + 1,
140 ) {
141 placed = true;
142 }
143 }
144 }
145 }
146 PlacementModifier::CountOnEveryLayer { count } => {
147 for position in Self::count_on_every_layer_positions(region, random, origin, count)
148 {
149 if Self::place_placed_feature_from_modifier(
150 region,
151 registry,
152 random,
153 position,
154 feature,
155 biome_filter,
156 biome_zoom_seed,
157 modifier_index + 1,
158 ) {
159 placed = true;
160 }
161 }
162 }
163 PlacementModifier::EnvironmentScan {
164 direction_of_search,
165 target_condition,
166 allowed_search_condition,
167 max_steps,
168 } => {
169 if let Some(position) = Self::environment_scan_position(
170 region,
171 registry,
172 origin,
173 *direction_of_search,
174 target_condition,
175 allowed_search_condition.as_ref(),
176 *max_steps,
177 ) {
178 placed = Self::place_placed_feature_from_modifier(
179 region,
180 registry,
181 random,
182 position,
183 feature,
184 biome_filter,
185 biome_zoom_seed,
186 modifier_index + 1,
187 );
188 }
189 }
190 PlacementModifier::FixedPlacement { positions } => {
191 let chunk_x = SectionPos::block_to_section_coord(origin.x());
192 let chunk_z = SectionPos::block_to_section_coord(origin.z());
193 for position in positions {
194 let position = BlockPos::new(position[0], position[1], position[2]);
195 if chunk_x != SectionPos::block_to_section_coord(position.x())
196 || chunk_z != SectionPos::block_to_section_coord(position.z())
197 {
198 continue;
199 }
200 if Self::place_placed_feature_from_modifier(
201 region,
202 registry,
203 random,
204 position,
205 feature,
206 biome_filter,
207 biome_zoom_seed,
208 modifier_index + 1,
209 ) {
210 placed = true;
211 }
212 }
213 }
214 PlacementModifier::HeightRange { height } => {
215 let position = BlockPos::new(
216 origin.x(),
217 height.sample(
218 random,
219 region.generation_min_y(),
220 region.generation_height(),
221 ),
222 origin.z(),
223 );
224 placed = Self::place_placed_feature_from_modifier(
225 region,
226 registry,
227 random,
228 position,
229 feature,
230 biome_filter,
231 biome_zoom_seed,
232 modifier_index + 1,
233 );
234 }
235 PlacementModifier::Heightmap { heightmap } => {
236 let height = region.height_at(
237 Self::feature_heightmap_type(*heightmap),
238 origin.x(),
239 origin.z(),
240 );
241 if height > region.min_y() {
242 placed = Self::place_placed_feature_from_modifier(
243 region,
244 registry,
245 random,
246 BlockPos::new(origin.x(), height, origin.z()),
247 feature,
248 biome_filter,
249 biome_zoom_seed,
250 modifier_index + 1,
251 );
252 }
253 }
254 PlacementModifier::InSquare => {
255 let position = BlockPos::new(
256 origin.x() + random.next_i32_bounded(16),
257 origin.y(),
258 origin.z() + random.next_i32_bounded(16),
259 );
260 placed = Self::place_placed_feature_from_modifier(
261 region,
262 registry,
263 random,
264 position,
265 feature,
266 biome_filter,
267 biome_zoom_seed,
268 modifier_index + 1,
269 );
270 }
271 PlacementModifier::NoiseBasedCount {
272 noise_to_count_ratio,
273 noise_factor,
274 noise_offset,
275 } => {
276 let noise = Self::biome_info_noise_value(
277 f64::from(origin.x()) / *noise_factor,
278 f64::from(origin.z()) / *noise_factor,
279 );
280 let count =
281 ((noise + *noise_offset) * f64::from(*noise_to_count_ratio)).ceil() as i32;
282 if let Ok(count) = usize::try_from(count) {
283 for _ in 0..count {
284 if Self::place_placed_feature_from_modifier(
285 region,
286 registry,
287 random,
288 origin,
289 feature,
290 biome_filter,
291 biome_zoom_seed,
292 modifier_index + 1,
293 ) {
294 placed = true;
295 }
296 }
297 }
298 }
299 PlacementModifier::NoiseThresholdCount {
300 noise_level,
301 below_noise,
302 above_noise,
303 } => {
304 let noise = Self::biome_info_noise_value(
305 f64::from(origin.x()) / 200.0,
306 f64::from(origin.z()) / 200.0,
307 );
308 let count = if noise < *noise_level {
309 *below_noise
310 } else {
311 *above_noise
312 };
313 if let Ok(count) = usize::try_from(count) {
314 for _ in 0..count {
315 if Self::place_placed_feature_from_modifier(
316 region,
317 registry,
318 random,
319 origin,
320 feature,
321 biome_filter,
322 biome_zoom_seed,
323 modifier_index + 1,
324 ) {
325 placed = true;
326 }
327 }
328 }
329 }
330 PlacementModifier::RandomOffset {
331 xz_spread,
332 y_spread,
333 } => {
334 let position = BlockPos::new(
335 origin.x() + xz_spread.sample(random),
336 origin.y() + y_spread.sample(random),
337 origin.z() + xz_spread.sample(random),
338 );
339 placed = Self::place_placed_feature_from_modifier(
340 region,
341 registry,
342 random,
343 position,
344 feature,
345 biome_filter,
346 biome_zoom_seed,
347 modifier_index + 1,
348 );
349 }
350 PlacementModifier::RarityFilter { chance } => {
351 assert!(
352 *chance > 0,
353 "rarity filter chance must be positive, got {chance}"
354 );
355 if random.next_f32() < 1.0 / (*chance as f32) {
356 placed = Self::place_placed_feature_from_modifier(
357 region,
358 registry,
359 random,
360 origin,
361 feature,
362 biome_filter,
363 biome_zoom_seed,
364 modifier_index + 1,
365 );
366 }
367 }
368 PlacementModifier::SurfaceRelativeThresholdFilter {
369 heightmap,
370 min_inclusive,
371 max_inclusive,
372 } => {
373 let surface_y = i64::from(region.height_at(
374 Self::feature_heightmap_type(*heightmap),
375 origin.x(),
376 origin.z(),
377 ));
378 let min_y = surface_y + i64::from(min_inclusive.unwrap_or(i32::MIN));
379 let max_y = surface_y + i64::from(max_inclusive.unwrap_or(i32::MAX));
380 let origin_y = i64::from(origin.y());
381 if min_y <= origin_y && origin_y <= max_y {
382 placed = Self::place_placed_feature_from_modifier(
383 region,
384 registry,
385 random,
386 origin,
387 feature,
388 biome_filter,
389 biome_zoom_seed,
390 modifier_index + 1,
391 );
392 }
393 }
394 PlacementModifier::SurfaceWaterDepthFilter { max_water_depth } => {
395 let ocean_floor =
396 region.height_at(HeightmapType::OceanFloor, origin.x(), origin.z());
397 let surface = region.height_at(HeightmapType::WorldSurface, origin.x(), origin.z());
398 if surface - ocean_floor <= *max_water_depth {
399 placed = Self::place_placed_feature_from_modifier(
400 region,
401 registry,
402 random,
403 origin,
404 feature,
405 biome_filter,
406 biome_zoom_seed,
407 modifier_index + 1,
408 );
409 }
410 }
411 }
412
413 placed
414 }
415
416 pub(super) fn place_placed_feature_ref(
417 region: &mut WorldGenRegion<'_>,
418 registry: &Registry,
419 random: &mut WorldgenRandom,
420 origin: BlockPos,
421 feature: &PlacedFeatureRef,
422 biome_zoom_seed: i64,
423 ) -> bool {
424 let feature_data = match feature {
425 PlacedFeatureRef::Reference(feature) => &feature.data,
426 PlacedFeatureRef::Inline(feature) => feature,
427 };
428
429 Self::place_placed_feature_data(
430 region,
431 registry,
432 random,
433 origin,
434 feature_data,
435 None,
436 biome_zoom_seed,
437 )
438 }
439
440 pub(crate) fn place_structure_pool_feature(
441 region: &mut WorldGenRegion<'_>,
442 registry: &Registry,
443 random: &mut WorldgenRandom,
444 origin: BlockPos,
445 feature_key: &Identifier,
446 biome_zoom_seed: i64,
447 ) -> bool {
448 let Some(feature) = registry.placed_features.by_key(feature_key) else {
449 panic!("template pool references unknown placed feature {feature_key}");
450 };
451
452 Self::place_placed_feature_from_modifier(
453 region,
454 registry,
455 random,
456 origin,
457 &feature.data,
458 BiomeFilterMode::Ignore,
459 biome_zoom_seed,
460 0,
461 )
462 }
463}