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 BlockStateProviderEvaluator::test_block_predicate(
116 region, registry, predicate, origin,
117 ) {
118 placed = Self::place_placed_feature_from_modifier(
119 region,
120 registry,
121 random,
122 origin,
123 feature,
124 biome_filter,
125 biome_zoom_seed,
126 modifier_index + 1,
127 );
128 }
129 }
130 PlacementModifier::Count { count } => {
131 if let Ok(count) = usize::try_from(count.sample(random)) {
132 for _ in 0..count {
133 if Self::place_placed_feature_from_modifier(
134 region,
135 registry,
136 random,
137 origin,
138 feature,
139 biome_filter,
140 biome_zoom_seed,
141 modifier_index + 1,
142 ) {
143 placed = true;
144 }
145 }
146 }
147 }
148 PlacementModifier::CountOnEveryLayer { count } => {
149 for position in Self::count_on_every_layer_positions(region, random, origin, count)
150 {
151 if Self::place_placed_feature_from_modifier(
152 region,
153 registry,
154 random,
155 position,
156 feature,
157 biome_filter,
158 biome_zoom_seed,
159 modifier_index + 1,
160 ) {
161 placed = true;
162 }
163 }
164 }
165 PlacementModifier::EnvironmentScan {
166 direction_of_search,
167 target_condition,
168 allowed_search_condition,
169 max_steps,
170 } => {
171 if let Some(position) = Self::environment_scan_position(
172 region,
173 registry,
174 origin,
175 *direction_of_search,
176 target_condition,
177 allowed_search_condition.as_ref(),
178 *max_steps,
179 ) {
180 placed = Self::place_placed_feature_from_modifier(
181 region,
182 registry,
183 random,
184 position,
185 feature,
186 biome_filter,
187 biome_zoom_seed,
188 modifier_index + 1,
189 );
190 }
191 }
192 PlacementModifier::FixedPlacement { positions } => {
193 let chunk_x = SectionPos::block_to_section_coord(origin.x());
194 let chunk_z = SectionPos::block_to_section_coord(origin.z());
195 for position in positions {
196 let position = BlockPos::new(position[0], position[1], position[2]);
197 if chunk_x != SectionPos::block_to_section_coord(position.x())
198 || chunk_z != SectionPos::block_to_section_coord(position.z())
199 {
200 continue;
201 }
202 if Self::place_placed_feature_from_modifier(
203 region,
204 registry,
205 random,
206 position,
207 feature,
208 biome_filter,
209 biome_zoom_seed,
210 modifier_index + 1,
211 ) {
212 placed = true;
213 }
214 }
215 }
216 PlacementModifier::HeightRange { height } => {
217 let position = BlockPos::new(
218 origin.x(),
219 height.sample(
220 random,
221 region.generation_min_y(),
222 region.generation_height(),
223 region.sea_level(),
224 ),
225 origin.z(),
226 );
227 placed = Self::place_placed_feature_from_modifier(
228 region,
229 registry,
230 random,
231 position,
232 feature,
233 biome_filter,
234 biome_zoom_seed,
235 modifier_index + 1,
236 );
237 }
238 PlacementModifier::Heightmap { heightmap } => {
239 let height = region.height_at(
240 Self::feature_heightmap_type(*heightmap),
241 origin.x(),
242 origin.z(),
243 );
244 if height > region.min_y() {
245 placed = Self::place_placed_feature_from_modifier(
246 region,
247 registry,
248 random,
249 BlockPos::new(origin.x(), height, origin.z()),
250 feature,
251 biome_filter,
252 biome_zoom_seed,
253 modifier_index + 1,
254 );
255 }
256 }
257 PlacementModifier::InSquare => {
258 let position = BlockPos::new(
259 origin.x() + random.next_i32_bounded(16),
260 origin.y(),
261 origin.z() + random.next_i32_bounded(16),
262 );
263 placed = Self::place_placed_feature_from_modifier(
264 region,
265 registry,
266 random,
267 position,
268 feature,
269 biome_filter,
270 biome_zoom_seed,
271 modifier_index + 1,
272 );
273 }
274 PlacementModifier::NoiseBasedCount {
275 noise_to_count_ratio,
276 noise_factor,
277 noise_offset,
278 } => {
279 let noise = Self::biome_info_noise_value(
280 f64::from(origin.x()) / *noise_factor,
281 f64::from(origin.z()) / *noise_factor,
282 );
283 let count =
284 ((noise + *noise_offset) * f64::from(*noise_to_count_ratio)).ceil() as i32;
285 if let Ok(count) = usize::try_from(count) {
286 for _ in 0..count {
287 if Self::place_placed_feature_from_modifier(
288 region,
289 registry,
290 random,
291 origin,
292 feature,
293 biome_filter,
294 biome_zoom_seed,
295 modifier_index + 1,
296 ) {
297 placed = true;
298 }
299 }
300 }
301 }
302 PlacementModifier::NoiseThresholdCount {
303 noise_level,
304 below_noise,
305 above_noise,
306 } => {
307 let noise = Self::biome_info_noise_value(
308 f64::from(origin.x()) / 200.0,
309 f64::from(origin.z()) / 200.0,
310 );
311 let count = if noise < *noise_level {
312 *below_noise
313 } else {
314 *above_noise
315 };
316 if let Ok(count) = usize::try_from(count) {
317 for _ in 0..count {
318 if Self::place_placed_feature_from_modifier(
319 region,
320 registry,
321 random,
322 origin,
323 feature,
324 biome_filter,
325 biome_zoom_seed,
326 modifier_index + 1,
327 ) {
328 placed = true;
329 }
330 }
331 }
332 }
333 PlacementModifier::RandomOffset {
334 xz_spread,
335 y_spread,
336 } => {
337 let position = BlockPos::new(
338 origin.x() + xz_spread.sample(random),
339 origin.y() + y_spread.sample(random),
340 origin.z() + xz_spread.sample(random),
341 );
342 placed = Self::place_placed_feature_from_modifier(
343 region,
344 registry,
345 random,
346 position,
347 feature,
348 biome_filter,
349 biome_zoom_seed,
350 modifier_index + 1,
351 );
352 }
353 PlacementModifier::RarityFilter { chance } => {
354 assert!(
355 *chance > 0,
356 "rarity filter chance must be positive, got {chance}"
357 );
358 if random.next_f32() < 1.0 / (*chance as f32) {
359 placed = Self::place_placed_feature_from_modifier(
360 region,
361 registry,
362 random,
363 origin,
364 feature,
365 biome_filter,
366 biome_zoom_seed,
367 modifier_index + 1,
368 );
369 }
370 }
371 PlacementModifier::SurfaceRelativeThresholdFilter {
372 heightmap,
373 min_inclusive,
374 max_inclusive,
375 } => {
376 let surface_y = i64::from(region.height_at(
377 Self::feature_heightmap_type(*heightmap),
378 origin.x(),
379 origin.z(),
380 ));
381 let min_y = surface_y + i64::from(min_inclusive.unwrap_or(i32::MIN));
382 let max_y = surface_y + i64::from(max_inclusive.unwrap_or(i32::MAX));
383 let origin_y = i64::from(origin.y());
384 if min_y <= origin_y && origin_y <= max_y {
385 placed = Self::place_placed_feature_from_modifier(
386 region,
387 registry,
388 random,
389 origin,
390 feature,
391 biome_filter,
392 biome_zoom_seed,
393 modifier_index + 1,
394 );
395 }
396 }
397 PlacementModifier::SurfaceWaterDepthFilter { max_water_depth } => {
398 let ocean_floor =
399 region.height_at(HeightmapType::OceanFloor, origin.x(), origin.z());
400 let surface = region.height_at(HeightmapType::WorldSurface, origin.x(), origin.z());
401 if surface - ocean_floor <= *max_water_depth {
402 placed = Self::place_placed_feature_from_modifier(
403 region,
404 registry,
405 random,
406 origin,
407 feature,
408 biome_filter,
409 biome_zoom_seed,
410 modifier_index + 1,
411 );
412 }
413 }
414 PlacementModifier::Offset { x, y, z } => {
415 let position = BlockPos::new(
416 origin.x() + x.sample(random),
417 origin.y() + y.sample(random),
418 origin.z() + z.sample(random),
419 );
420 placed = Self::place_placed_feature_from_modifier(
421 region,
422 registry,
423 random,
424 position,
425 feature,
426 biome_filter,
427 biome_zoom_seed,
428 modifier_index + 1,
429 );
430 }
431 PlacementModifier::RandomChance { chance } => {
432 if random.next_f32() < *chance {
433 placed = Self::place_placed_feature_from_modifier(
434 region,
435 registry,
436 random,
437 origin,
438 feature,
439 biome_filter,
440 biome_zoom_seed,
441 modifier_index + 1,
442 );
443 }
444 }
445 PlacementModifier::RandomlySelected { placements } => {
446 let Ok(count) = i32::try_from(placements.len()) else {
447 panic!("vanilla randomly selected placement count exceeds i32")
448 };
449
450 let index = random.next_i32_bounded(count) as usize;
451 let modifier = &placements[index];
452 match modifier {
453 PlacementModifier::Offset { x, y, z } => {
454 let position = BlockPos::new(
455 origin.x() + x.sample(random),
456 origin.y() + y.sample(random),
457 origin.z() + z.sample(random),
458 );
459 placed = Self::place_placed_feature_from_modifier(
460 region,
461 registry,
462 random,
463 position,
464 feature,
465 biome_filter,
466 biome_zoom_seed,
467 modifier_index + 1,
468 );
469 }
470 _ => panic!("unsupported nested vanilla placement modifier: {modifier:?}"),
471 }
472 }
473 PlacementModifier::Cuboid {
474 xz_size,
475 y_size,
476 include_edges,
477 include_interior,
478 } => {
479 let height = y_size.sample(random);
480 let width = xz_size.sample(random);
481 let length = xz_size.sample(random);
482 for x in 0..=width {
483 for y in 0..=height {
484 for z in 0..=length {
485 let included =
486 (*include_edges || x != 0 && x != width || y != 0 && y != height)
487 && (*include_edges
488 || z != 0 && z != length
489 || y != 0 && y != height)
490 && (*include_edges
491 || x != 0 && x != width
492 || z != 0 && z != length)
493 && (*include_interior
494 || x == 0
495 || x == width
496 || y == 0
497 || y == height
498 || z == 0
499 || z == length);
500 if !included {
501 continue;
502 }
503 let position =
504 BlockPos::new(origin.x() + x, origin.y() + y, origin.z() + z);
505 if Self::place_placed_feature_from_modifier(
506 region,
507 registry,
508 random,
509 position,
510 feature,
511 biome_filter,
512 biome_zoom_seed,
513 modifier_index + 1,
514 ) {
515 placed = true;
516 }
517 }
518 }
519 }
520 }
521 }
522
523 placed
524 }
525
526 pub(super) fn place_placed_feature_ref(
527 region: &mut WorldGenRegion<'_>,
528 registry: &Registry,
529 random: &mut WorldgenRandom,
530 origin: BlockPos,
531 feature: &PlacedFeatureRef,
532 biome_zoom_seed: i64,
533 ) -> bool {
534 let feature_data = match feature {
535 PlacedFeatureRef::Reference(feature) => &feature.data,
536 PlacedFeatureRef::Inline(feature) => feature,
537 };
538
539 Self::place_placed_feature_data(
540 region,
541 registry,
542 random,
543 origin,
544 feature_data,
545 None,
546 biome_zoom_seed,
547 )
548 }
549
550 pub(crate) fn place_structure_pool_feature(
551 region: &mut WorldGenRegion<'_>,
552 registry: &Registry,
553 random: &mut WorldgenRandom,
554 origin: BlockPos,
555 feature_key: &Identifier,
556 biome_zoom_seed: i64,
557 ) -> bool {
558 let Some(feature) = registry.placed_features.by_key(feature_key) else {
559 panic!("template pool references unknown placed feature {feature_key}");
560 };
561
562 Self::place_placed_feature_from_modifier(
563 region,
564 registry,
565 random,
566 origin,
567 &feature.data,
568 BiomeFilterMode::Ignore,
569 biome_zoom_seed,
570 0,
571 )
572 }
573}