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 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}