Skip to main content

steel_core/worldgen/feature/
placed.rs

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}