Skip to main content

steel_core/worldgen/feature/
configured.rs

1use super::prelude::*;
2use super::runner::FeatureDecorationRunner;
3use crate::worldgen::template::{
4    StructurePlaceSettings, StructureProcessorRandom, StructureTemplate,
5};
6use glam::IVec3;
7use steel_registry::structure::LiquidSettingsData;
8use steel_utils::BoundingBox;
9use steel_worldgen::structure::{StructureBlockIgnore, StructureMirror};
10
11struct ConfiguredFeaturePlaceContext<'a, 'region> {
12    region: &'a mut WorldGenRegion<'region>,
13    registry: &'a Registry,
14    random: &'a mut WorldgenRandom,
15    origin: BlockPos,
16    biome_zoom_seed: i64,
17}
18
19type ConfiguredFeaturePlacer = for<'a, 'region> fn(
20    &mut ConfiguredFeaturePlaceContext<'a, 'region>,
21    &ConfiguredFeatureKind,
22) -> bool;
23
24impl FeatureDecorationRunner {
25    pub(super) fn place_configured_feature(
26        region: &mut WorldGenRegion<'_>,
27        registry: &Registry,
28        random: &mut WorldgenRandom,
29        feature: &ConfiguredFeatureRef,
30        origin: BlockPos,
31        biome_zoom_seed: i64,
32    ) -> bool {
33        let kind = Self::configured_feature_kind(feature);
34        Self::place_configured_feature_kind(region, registry, random, kind, origin, biome_zoom_seed)
35    }
36
37    pub(super) fn place_configured_feature_kind(
38        region: &mut WorldGenRegion<'_>,
39        registry: &Registry,
40        random: &mut WorldgenRandom,
41        kind: &ConfiguredFeatureKind,
42        origin: BlockPos,
43        biome_zoom_seed: i64,
44    ) -> bool {
45        if !region.can_write_to_chunk(
46            SectionPos::block_to_section_coord(origin.x()),
47            SectionPos::block_to_section_coord(origin.z()),
48        ) {
49            return false;
50        }
51
52        let placer = Self::configured_feature_placer(kind);
53        let mut context = ConfiguredFeaturePlaceContext {
54            region,
55            registry,
56            random,
57            origin,
58            biome_zoom_seed,
59        };
60        placer(&mut context, kind)
61    }
62
63    pub(super) fn configured_feature_kind(
64        feature: &ConfiguredFeatureRef,
65    ) -> &ConfiguredFeatureKind {
66        match feature {
67            ConfiguredFeatureRef::Reference(configured_feature) => &configured_feature.kind,
68            ConfiguredFeatureRef::Inline(configured_feature) => configured_feature,
69        }
70    }
71
72    fn configured_feature_placer(kind: &ConfiguredFeatureKind) -> ConfiguredFeaturePlacer {
73        match kind {
74            ConfiguredFeatureKind::Bamboo(_) => place_bamboo,
75            ConfiguredFeatureKind::BasaltColumns(_) => place_basalt_columns,
76            ConfiguredFeatureKind::BasaltPillar => place_basalt_pillar,
77            ConfiguredFeatureKind::BlockBlob(_) => place_block_blob,
78            ConfiguredFeatureKind::BlockColumn(_) => place_block_column,
79            ConfiguredFeatureKind::BlockPile(_) => place_block_pile,
80            ConfiguredFeatureKind::BlueIce => place_blue_ice,
81            ConfiguredFeatureKind::BonusChest => place_bonus_chest,
82            ConfiguredFeatureKind::ChorusPlant => place_chorus_plant,
83            ConfiguredFeatureKind::CoralClaw { .. } => place_coral_claw,
84            ConfiguredFeatureKind::CoralMushroom => place_coral_mushroom,
85            ConfiguredFeatureKind::CoralTree { .. } => place_coral_tree,
86            ConfiguredFeatureKind::DeltaFeature(_) => place_delta_feature,
87            ConfiguredFeatureKind::DesertWell => place_desert_well,
88            ConfiguredFeatureKind::Disk(_) => place_disk,
89            ConfiguredFeatureKind::DripstoneCluster(_) => place_dripstone_cluster,
90            ConfiguredFeatureKind::EndGateway(_) => place_end_gateway,
91            ConfiguredFeatureKind::EndIsland => place_end_island,
92            ConfiguredFeatureKind::EndPlatform => place_end_platform,
93            ConfiguredFeatureKind::EndPodium { .. } => place_end_podium,
94            ConfiguredFeatureKind::EndSpike(_) => place_end_spike,
95            ConfiguredFeatureKind::FallenTree(_) => place_fallen_tree,
96            ConfiguredFeatureKind::Fossil(_) => place_fossil,
97            ConfiguredFeatureKind::FreezeTopLayer => place_freeze_top_layer,
98            ConfiguredFeatureKind::Geode(_) => place_geode,
99            ConfiguredFeatureKind::GlowstoneBlob => place_glowstone_blob,
100            ConfiguredFeatureKind::HugeBrownMushroom(_) => place_huge_brown_mushroom,
101            ConfiguredFeatureKind::HugeFungus(_) => place_huge_fungus,
102            ConfiguredFeatureKind::HugeRedMushroom(_) => place_huge_red_mushroom,
103            ConfiguredFeatureKind::Iceberg(_) => place_iceberg,
104            ConfiguredFeatureKind::Kelp => place_kelp,
105            ConfiguredFeatureKind::Lake(_) => place_lake,
106            ConfiguredFeatureKind::LargeDripstone(_) => place_large_dripstone,
107            ConfiguredFeatureKind::MonsterRoom => place_monster_room,
108            ConfiguredFeatureKind::NoOp => place_no_op,
109            ConfiguredFeatureKind::MultifaceGrowth(_) => place_multiface_growth,
110            ConfiguredFeatureKind::NetherForestVegetation(_) => place_nether_forest_vegetation,
111            ConfiguredFeatureKind::NetherrackReplaceBlobs(_) => place_netherrack_replace_blobs,
112            ConfiguredFeatureKind::Ore(_) => place_ore,
113            ConfiguredFeatureKind::Overlay { .. } => place_overlay,
114            ConfiguredFeatureKind::ProjectedRandomPatchySquare(_) => {
115                place_projected_random_patchy_square
116            }
117            ConfiguredFeatureKind::PointedDripstone(_) => place_pointed_dripstone,
118            ConfiguredFeatureKind::RandomBooleanSelector(_) => place_random_boolean_selector,
119            ConfiguredFeatureKind::RandomNeighborSpread(_) => place_random_neighbor_spread,
120            ConfiguredFeatureKind::RandomSelector(_) => place_random_selector,
121            ConfiguredFeatureKind::WeightedRandomSelector(_) => place_weighted_random_selector,
122            ConfiguredFeatureKind::RootSystem(_) => place_root_system,
123            ConfiguredFeatureKind::ScatteredOre(_) => place_scattered_ore,
124            ConfiguredFeatureKind::SculkPatch(_) => place_sculk_patch,
125            ConfiguredFeatureKind::SeaPickle(_) => place_sea_pickle,
126            ConfiguredFeatureKind::Seagrass(_) => place_seagrass,
127            ConfiguredFeatureKind::Sequence(_) => place_sequence,
128            ConfiguredFeatureKind::SimpleBlock(_) => place_simple_block,
129            ConfiguredFeatureKind::SimpleRandomSelector(_) => place_simple_random_selector,
130            ConfiguredFeatureKind::SingleBlockPillar(_) => place_single_block_pillar,
131            ConfiguredFeatureKind::Speleothem(_) => place_speleothem,
132            ConfiguredFeatureKind::SpeleothemCluster(_) => place_speleothem_cluster,
133            ConfiguredFeatureKind::Spike(_) => place_spike,
134            ConfiguredFeatureKind::SpringFeature(_) => place_spring_feature,
135            ConfiguredFeatureKind::SteppedColumnCluster(_) => place_stepped_column_cluster,
136            ConfiguredFeatureKind::Template(_) => place_template,
137            ConfiguredFeatureKind::Tree(_) => place_tree,
138            ConfiguredFeatureKind::TwistingVines(_) => place_twisting_vines,
139            ConfiguredFeatureKind::UnderwaterMagma(_) => place_underwater_magma,
140            ConfiguredFeatureKind::VegetationPatch(_) => place_vegetation_patch,
141            ConfiguredFeatureKind::Vines => place_vines,
142            ConfiguredFeatureKind::VoidStartPlatform => place_void_start_platform,
143            ConfiguredFeatureKind::WaterloggedVegetationPatch(_) => {
144                place_waterlogged_vegetation_patch
145            }
146            ConfiguredFeatureKind::WeepingVines => place_weeping_vines,
147        }
148    }
149}
150
151fn place_end_podium(
152    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
153    kind: &ConfiguredFeatureKind,
154) -> bool {
155    let ConfiguredFeatureKind::EndPodium { active } = kind else {
156        panic!("end_podium placer received wrong configured feature kind");
157    };
158    FeatureDecorationRunner::place_end_podium_feature(context.region, context.origin, *active)
159}
160
161const fn place_no_op(
162    _context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
163    _kind: &ConfiguredFeatureKind,
164) -> bool {
165    true
166}
167
168fn place_projected_random_patchy_square(
169    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
170    kind: &ConfiguredFeatureKind,
171) -> bool {
172    let ConfiguredFeatureKind::ProjectedRandomPatchySquare(config) = kind else {
173        panic!("projected_random_patchy_square placer received wrong configured feature kind");
174    };
175    FeatureDecorationRunner::place_projected_random_patchy_square_feature(
176        context.region,
177        context.registry,
178        context.random,
179        config,
180        context.origin,
181    )
182}
183
184fn place_single_block_pillar(
185    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
186    kind: &ConfiguredFeatureKind,
187) -> bool {
188    let ConfiguredFeatureKind::SingleBlockPillar(config) = kind else {
189        panic!("single_block_pillar placer received wrong configured feature kind");
190    };
191    FeatureDecorationRunner::place_single_block_pillar_feature(
192        context.region,
193        context.registry,
194        context.random,
195        config,
196        context.origin,
197        context.biome_zoom_seed,
198    )
199}
200
201fn place_overlay(
202    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
203    kind: &ConfiguredFeatureKind,
204) -> bool {
205    let ConfiguredFeatureKind::Overlay { features } = kind else {
206        panic!("overlay placer received wrong configured feature kind");
207    };
208    let mut placed_any = false;
209    for feature in features {
210        placed_any |= FeatureDecorationRunner::place_placed_feature_ref(
211            context.region,
212            context.registry,
213            context.random,
214            context.origin,
215            feature,
216            context.biome_zoom_seed,
217        );
218    }
219    placed_any
220}
221
222fn place_random_neighbor_spread(
223    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
224    kind: &ConfiguredFeatureKind,
225) -> bool {
226    let ConfiguredFeatureKind::RandomNeighborSpread(config) = kind else {
227        panic!("random_neighbor_spread placer received wrong configured feature kind");
228    };
229    FeatureDecorationRunner::place_random_neighbor_spread_feature(
230        context.region,
231        context.registry,
232        context.random,
233        config,
234        context.origin,
235    )
236}
237
238fn place_stepped_column_cluster(
239    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
240    kind: &ConfiguredFeatureKind,
241) -> bool {
242    let ConfiguredFeatureKind::SteppedColumnCluster(config) = kind else {
243        panic!("stepped_column_cluster placer received wrong configured feature kind");
244    };
245    FeatureDecorationRunner::place_stepped_column_cluster_feature(
246        context.region,
247        context.registry,
248        context.random,
249        config,
250        context.origin,
251    )
252}
253
254fn place_random_boolean_selector(
255    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
256    kind: &ConfiguredFeatureKind,
257) -> bool {
258    let ConfiguredFeatureKind::RandomBooleanSelector(config) = kind else {
259        panic!("random_boolean_selector placer received wrong configured feature kind");
260    };
261    let selected_feature = if context.random.next_bool() {
262        &config.feature_true
263    } else {
264        &config.feature_false
265    };
266    FeatureDecorationRunner::place_placed_feature_ref(
267        context.region,
268        context.registry,
269        context.random,
270        context.origin,
271        selected_feature,
272        context.biome_zoom_seed,
273    )
274}
275
276fn place_random_selector(
277    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
278    kind: &ConfiguredFeatureKind,
279) -> bool {
280    let ConfiguredFeatureKind::RandomSelector(config) = kind else {
281        panic!("random_selector placer received wrong configured feature kind");
282    };
283    for weighted_feature in &config.features {
284        let roll = context.random.next_f32();
285        if roll < weighted_feature.chance {
286            return FeatureDecorationRunner::place_placed_feature_ref(
287                context.region,
288                context.registry,
289                context.random,
290                context.origin,
291                &weighted_feature.feature,
292                context.biome_zoom_seed,
293            );
294        }
295    }
296
297    FeatureDecorationRunner::place_placed_feature_ref(
298        context.region,
299        context.registry,
300        context.random,
301        context.origin,
302        &config.default,
303        context.biome_zoom_seed,
304    )
305}
306
307fn place_weighted_random_selector(
308    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
309    kind: &ConfiguredFeatureKind,
310) -> bool {
311    let ConfiguredFeatureKind::WeightedRandomSelector(config) = kind else {
312        panic!("weighted_random_selector placer received wrong configured feature kind");
313    };
314    let Some(feature_index) = weighted_index(
315        config.features.len(),
316        |index| config.features[index].weight,
317        context.random,
318    ) else {
319        return false;
320    };
321    FeatureDecorationRunner::place_placed_feature_ref(
322        context.region,
323        context.registry,
324        context.random,
325        context.origin,
326        &config.features[feature_index].data,
327        context.biome_zoom_seed,
328    )
329}
330
331fn place_simple_random_selector(
332    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
333    kind: &ConfiguredFeatureKind,
334) -> bool {
335    let ConfiguredFeatureKind::SimpleRandomSelector(config) = kind else {
336        panic!("simple_random_selector placer received wrong configured feature kind");
337    };
338    assert!(
339        !config.features.is_empty(),
340        "simple random selector feature list must not be empty"
341    );
342    let Ok(feature_count) = i32::try_from(config.features.len()) else {
343        panic!(
344            "simple random selector feature count {} exceeds i32 range",
345            config.features.len()
346        );
347    };
348    let feature_index = context.random.next_i32_bounded(feature_count) as usize;
349    FeatureDecorationRunner::place_placed_feature_ref(
350        context.region,
351        context.registry,
352        context.random,
353        context.origin,
354        &config.features[feature_index],
355        context.biome_zoom_seed,
356    )
357}
358
359fn place_sequence(
360    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
361    kind: &ConfiguredFeatureKind,
362) -> bool {
363    let ConfiguredFeatureKind::Sequence(config) = kind else {
364        panic!("sequence placer received wrong configured feature kind");
365    };
366    for feature in &config.features {
367        if !FeatureDecorationRunner::place_placed_feature_ref(
368            context.region,
369            context.registry,
370            context.random,
371            context.origin,
372            feature,
373            context.biome_zoom_seed,
374        ) {
375            return false;
376        }
377    }
378    true
379}
380
381fn place_template(
382    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
383    kind: &ConfiguredFeatureKind,
384) -> bool {
385    let ConfiguredFeatureKind::Template(config) = kind else {
386        panic!("template placer received wrong configured feature kind");
387    };
388    let Some(template_index) = weighted_index(
389        config.templates.len(),
390        |index| config.templates[index].weight,
391        context.random,
392    ) else {
393        panic!("template feature has no selectable templates");
394    };
395    let entry = &config.templates[template_index].data;
396    let Ok(rotation_count) = i32::try_from(entry.rotations.len()) else {
397        panic!(
398            "template feature rotation count {} exceeds i32 range",
399            entry.rotations.len()
400        );
401    };
402    assert!(
403        rotation_count != 0,
404        "template feature entry {} has no rotations",
405        entry.id
406    );
407    let rotation = entry.rotations[context.random.next_i32_bounded(rotation_count) as usize];
408    let template = match StructureTemplate::load_vanilla(context.registry, &entry.id) {
409        Ok(template) => template,
410        Err(err) => panic!("{err}"),
411    };
412    let size = template.size(Rotation::None);
413    let position = template_feature_position(context.origin, rotation, size);
414    let settings = StructurePlaceSettings {
415        mirror: StructureMirror::None,
416        rotation,
417        rotation_pivot: BlockPos::ZERO,
418        bounding_box: template_feature_bounding_box(context.region),
419        processors: &[],
420        block_ignore: StructureBlockIgnore::None,
421        late_block_ignore: StructureBlockIgnore::None,
422        replace_jigsaws: false,
423        projection: None,
424        processor_random: StructureProcessorRandom::Placement,
425        liquid_settings: LiquidSettingsData::ApplyWaterlogging,
426    };
427
428    template.place_in_world(
429        context.region,
430        context.registry,
431        position,
432        position,
433        &settings,
434        context.random,
435        UpdateFlags::UPDATE_ALL,
436    )
437}
438
439fn weighted_index(
440    len: usize,
441    weight_at: impl Fn(usize) -> u32,
442    random: &mut WorldgenRandom,
443) -> Option<usize> {
444    let total_weight = (0..len)
445        .map(|index| u64::from(weight_at(index)))
446        .sum::<u64>();
447    if total_weight == 0 {
448        return None;
449    }
450    let Ok(total_weight_i32) = i32::try_from(total_weight) else {
451        panic!("weighted feature total weight {total_weight} exceeds i32 range");
452    };
453    let mut selection = random.next_i32_bounded(total_weight_i32) as u64;
454    for index in 0..len {
455        let weight = u64::from(weight_at(index));
456        if selection < weight {
457            return Some(index);
458        }
459        selection -= weight;
460    }
461    None
462}
463
464const fn template_feature_position(origin: BlockPos, rotation: Rotation, size: IVec3) -> BlockPos {
465    let west_offset = rotation.rotate(Direction::West).offset();
466    let north_offset = rotation.rotate(Direction::North).offset();
467    origin.offset(
468        west_offset.0 * (size.x / 2) + north_offset.0 * (size.z / 2),
469        0,
470        west_offset.2 * (size.x / 2) + north_offset.2 * (size.z / 2),
471    )
472}
473
474const fn template_feature_bounding_box(region: &WorldGenRegion<'_>) -> BoundingBox {
475    BoundingBox::new(
476        IVec3::new(i32::MIN, region.min_y(), i32::MIN),
477        IVec3::new(i32::MAX, region.max_y_exclusive() - 1, i32::MAX),
478    )
479}
480
481fn place_bamboo(
482    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
483    kind: &ConfiguredFeatureKind,
484) -> bool {
485    let ConfiguredFeatureKind::Bamboo(config) = kind else {
486        panic!("bamboo placer received wrong configured feature kind");
487    };
488    FeatureDecorationRunner::place_bamboo_feature(
489        context.region,
490        context.random,
491        config,
492        context.origin,
493    )
494}
495
496fn place_simple_block(
497    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
498    kind: &ConfiguredFeatureKind,
499) -> bool {
500    let ConfiguredFeatureKind::SimpleBlock(config) = kind else {
501        panic!("simple_block placer received wrong configured feature kind");
502    };
503    FeatureDecorationRunner::place_simple_block_feature(
504        context.region,
505        context.registry,
506        context.random,
507        config,
508        context.origin,
509    )
510}
511
512fn place_block_blob(
513    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
514    kind: &ConfiguredFeatureKind,
515) -> bool {
516    let ConfiguredFeatureKind::BlockBlob(config) = kind else {
517        panic!("block_blob placer received wrong configured feature kind");
518    };
519    FeatureDecorationRunner::place_block_blob_feature(
520        context.region,
521        context.registry,
522        context.random,
523        config,
524        context.origin,
525    )
526}
527
528fn place_vegetation_patch(
529    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
530    kind: &ConfiguredFeatureKind,
531) -> bool {
532    let ConfiguredFeatureKind::VegetationPatch(config) = kind else {
533        panic!("vegetation_patch placer received wrong configured feature kind");
534    };
535    FeatureDecorationRunner::place_vegetation_patch_feature(
536        context.region,
537        context.registry,
538        context.random,
539        config,
540        context.origin,
541        context.biome_zoom_seed,
542    )
543}
544
545fn place_waterlogged_vegetation_patch(
546    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
547    kind: &ConfiguredFeatureKind,
548) -> bool {
549    let ConfiguredFeatureKind::WaterloggedVegetationPatch(config) = kind else {
550        panic!("waterlogged_vegetation_patch placer received wrong configured feature kind");
551    };
552    FeatureDecorationRunner::place_waterlogged_vegetation_patch_feature(
553        context.region,
554        context.registry,
555        context.random,
556        config,
557        context.origin,
558        context.biome_zoom_seed,
559    )
560}
561
562fn place_block_column(
563    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
564    kind: &ConfiguredFeatureKind,
565) -> bool {
566    let ConfiguredFeatureKind::BlockColumn(config) = kind else {
567        panic!("block_column placer received wrong configured feature kind");
568    };
569    FeatureDecorationRunner::place_block_column_feature(
570        context.region,
571        context.registry,
572        context.random,
573        config,
574        context.origin,
575    )
576}
577
578fn place_block_pile(
579    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
580    kind: &ConfiguredFeatureKind,
581) -> bool {
582    let ConfiguredFeatureKind::BlockPile(config) = kind else {
583        panic!("block_pile placer received wrong configured feature kind");
584    };
585    FeatureDecorationRunner::place_block_pile_feature(
586        context.region,
587        context.registry,
588        context.random,
589        config,
590        context.origin,
591    )
592}
593
594fn place_disk(
595    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
596    kind: &ConfiguredFeatureKind,
597) -> bool {
598    let ConfiguredFeatureKind::Disk(config) = kind else {
599        panic!("disk placer received wrong configured feature kind");
600    };
601    FeatureDecorationRunner::place_disk_feature(
602        context.region,
603        context.registry,
604        context.random,
605        config,
606        context.origin,
607    )
608}
609
610fn place_basalt_pillar(
611    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
612    kind: &ConfiguredFeatureKind,
613) -> bool {
614    let ConfiguredFeatureKind::BasaltPillar = kind else {
615        panic!("basalt_pillar placer received wrong configured feature kind");
616    };
617    FeatureDecorationRunner::place_basalt_pillar_feature(
618        context.region,
619        context.random,
620        context.origin,
621    )
622}
623
624fn place_basalt_columns(
625    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
626    kind: &ConfiguredFeatureKind,
627) -> bool {
628    let ConfiguredFeatureKind::BasaltColumns(config) = kind else {
629        panic!("basalt_columns placer received wrong configured feature kind");
630    };
631    FeatureDecorationRunner::place_basalt_columns_feature(
632        context.region,
633        context.random,
634        config,
635        context.origin,
636    )
637}
638
639fn place_blue_ice(
640    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
641    kind: &ConfiguredFeatureKind,
642) -> bool {
643    let ConfiguredFeatureKind::BlueIce = kind else {
644        panic!("blue_ice placer received wrong configured feature kind");
645    };
646    FeatureDecorationRunner::place_blue_ice_feature(context.region, context.random, context.origin)
647}
648
649fn place_bonus_chest(
650    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
651    kind: &ConfiguredFeatureKind,
652) -> bool {
653    let ConfiguredFeatureKind::BonusChest = kind else {
654        panic!("bonus_chest placer received wrong configured feature kind");
655    };
656    FeatureDecorationRunner::place_bonus_chest_feature(
657        context.region,
658        context.random,
659        context.origin,
660    )
661}
662
663fn place_chorus_plant(
664    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
665    kind: &ConfiguredFeatureKind,
666) -> bool {
667    let ConfiguredFeatureKind::ChorusPlant = kind else {
668        panic!("chorus_plant placer received wrong configured feature kind");
669    };
670    FeatureDecorationRunner::place_chorus_plant_feature(
671        context.region,
672        context.random,
673        context.origin,
674    )
675}
676
677fn place_coral_claw(
678    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
679    kind: &ConfiguredFeatureKind,
680) -> bool {
681    let ConfiguredFeatureKind::CoralClaw { .. } = kind else {
682        panic!("coral_claw placer received wrong configured feature kind");
683    };
684    FeatureDecorationRunner::place_coral_claw_feature(
685        context.region,
686        context.registry,
687        context.random,
688        context.origin,
689    )
690}
691
692fn place_coral_mushroom(
693    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
694    kind: &ConfiguredFeatureKind,
695) -> bool {
696    let ConfiguredFeatureKind::CoralMushroom = kind else {
697        panic!("coral_mushroom placer received wrong configured feature kind");
698    };
699    FeatureDecorationRunner::place_coral_mushroom_feature(
700        context.region,
701        context.registry,
702        context.random,
703        context.origin,
704    )
705}
706
707fn place_coral_tree(
708    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
709    kind: &ConfiguredFeatureKind,
710) -> bool {
711    let ConfiguredFeatureKind::CoralTree { .. } = kind else {
712        panic!("coral_tree placer received wrong configured feature kind");
713    };
714    FeatureDecorationRunner::place_coral_tree_feature(
715        context.region,
716        context.registry,
717        context.random,
718        context.origin,
719    )
720}
721
722fn place_delta_feature(
723    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
724    kind: &ConfiguredFeatureKind,
725) -> bool {
726    let ConfiguredFeatureKind::DeltaFeature(config) = kind else {
727        panic!("delta_feature placer received wrong configured feature kind");
728    };
729    FeatureDecorationRunner::place_delta_feature(
730        context.region,
731        context.registry,
732        context.random,
733        config,
734        context.origin,
735    )
736}
737
738fn place_desert_well(
739    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
740    kind: &ConfiguredFeatureKind,
741) -> bool {
742    let ConfiguredFeatureKind::DesertWell = kind else {
743        panic!("desert_well placer received wrong configured feature kind");
744    };
745    FeatureDecorationRunner::place_desert_well_feature(
746        context.region,
747        context.random,
748        context.origin,
749    )
750}
751
752fn place_end_gateway(
753    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
754    kind: &ConfiguredFeatureKind,
755) -> bool {
756    let ConfiguredFeatureKind::EndGateway(config) = kind else {
757        panic!("end_gateway placer received wrong configured feature kind");
758    };
759    FeatureDecorationRunner::place_end_gateway_feature(context.region, config, context.origin)
760}
761
762fn place_end_island(
763    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
764    kind: &ConfiguredFeatureKind,
765) -> bool {
766    let ConfiguredFeatureKind::EndIsland = kind else {
767        panic!("end_island placer received wrong configured feature kind");
768    };
769    FeatureDecorationRunner::place_end_island_feature(
770        context.region,
771        context.random,
772        context.origin,
773    )
774}
775
776fn place_end_platform(
777    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
778    kind: &ConfiguredFeatureKind,
779) -> bool {
780    let ConfiguredFeatureKind::EndPlatform = kind else {
781        panic!("end_platform placer received wrong configured feature kind");
782    };
783    FeatureDecorationRunner::place_end_platform_feature(context.region, context.origin)
784}
785
786fn place_end_spike(
787    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
788    kind: &ConfiguredFeatureKind,
789) -> bool {
790    let ConfiguredFeatureKind::EndSpike(config) = kind else {
791        panic!("end_spike placer received wrong configured feature kind");
792    };
793    FeatureDecorationRunner::place_end_spike_feature(
794        context.region,
795        context.random,
796        config,
797        context.origin,
798    )
799}
800
801fn place_geode(
802    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
803    kind: &ConfiguredFeatureKind,
804) -> bool {
805    let ConfiguredFeatureKind::Geode(config) = kind else {
806        panic!("geode placer received wrong configured feature kind");
807    };
808    FeatureDecorationRunner::place_geode_feature(
809        context.region,
810        context.registry,
811        context.random,
812        config,
813        context.origin,
814    )
815}
816
817fn place_glowstone_blob(
818    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
819    kind: &ConfiguredFeatureKind,
820) -> bool {
821    let ConfiguredFeatureKind::GlowstoneBlob = kind else {
822        panic!("glowstone_blob placer received wrong configured feature kind");
823    };
824    FeatureDecorationRunner::place_glowstone_blob_feature(
825        context.region,
826        context.random,
827        context.origin,
828    )
829}
830
831fn place_huge_brown_mushroom(
832    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
833    kind: &ConfiguredFeatureKind,
834) -> bool {
835    let ConfiguredFeatureKind::HugeBrownMushroom(config) = kind else {
836        panic!("huge_brown_mushroom placer received wrong configured feature kind");
837    };
838    FeatureDecorationRunner::place_huge_brown_mushroom_feature(
839        context.region,
840        context.registry,
841        context.random,
842        config,
843        context.origin,
844    )
845}
846
847fn place_huge_red_mushroom(
848    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
849    kind: &ConfiguredFeatureKind,
850) -> bool {
851    let ConfiguredFeatureKind::HugeRedMushroom(config) = kind else {
852        panic!("huge_red_mushroom placer received wrong configured feature kind");
853    };
854    FeatureDecorationRunner::place_huge_red_mushroom_feature(
855        context.region,
856        context.registry,
857        context.random,
858        config,
859        context.origin,
860    )
861}
862
863fn place_huge_fungus(
864    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
865    kind: &ConfiguredFeatureKind,
866) -> bool {
867    let ConfiguredFeatureKind::HugeFungus(config) = kind else {
868        panic!("huge_fungus placer received wrong configured feature kind");
869    };
870    FeatureDecorationRunner::place_huge_fungus_feature(
871        context.region,
872        context.registry,
873        context.random,
874        config,
875        context.origin,
876    )
877}
878
879fn place_iceberg(
880    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
881    kind: &ConfiguredFeatureKind,
882) -> bool {
883    let ConfiguredFeatureKind::Iceberg(config) = kind else {
884        panic!("iceberg placer received wrong configured feature kind");
885    };
886    FeatureDecorationRunner::place_iceberg_feature(
887        context.region,
888        context.registry,
889        context.random,
890        config,
891        context.origin,
892    )
893}
894
895fn place_netherrack_replace_blobs(
896    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
897    kind: &ConfiguredFeatureKind,
898) -> bool {
899    let ConfiguredFeatureKind::NetherrackReplaceBlobs(config) = kind else {
900        panic!("netherrack_replace_blobs placer received wrong configured feature kind");
901    };
902    FeatureDecorationRunner::place_netherrack_replace_blobs_feature(
903        context.region,
904        context.registry,
905        context.random,
906        config,
907        context.origin,
908    )
909}
910
911fn place_nether_forest_vegetation(
912    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
913    kind: &ConfiguredFeatureKind,
914) -> bool {
915    let ConfiguredFeatureKind::NetherForestVegetation(config) = kind else {
916        panic!("nether_forest_vegetation placer received wrong configured feature kind");
917    };
918    FeatureDecorationRunner::place_nether_forest_vegetation_feature(
919        context.region,
920        context.registry,
921        context.random,
922        config,
923        context.origin,
924    )
925}
926
927fn place_twisting_vines(
928    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
929    kind: &ConfiguredFeatureKind,
930) -> bool {
931    let ConfiguredFeatureKind::TwistingVines(config) = kind else {
932        panic!("twisting_vines placer received wrong configured feature kind");
933    };
934    FeatureDecorationRunner::place_twisting_vines_feature(
935        context.region,
936        context.random,
937        config,
938        context.origin,
939    )
940}
941
942fn place_vines(
943    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
944    kind: &ConfiguredFeatureKind,
945) -> bool {
946    let ConfiguredFeatureKind::Vines = kind else {
947        panic!("vines placer received wrong configured feature kind");
948    };
949    FeatureDecorationRunner::place_vines_feature(context.region, context.origin)
950}
951
952fn place_void_start_platform(
953    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
954    kind: &ConfiguredFeatureKind,
955) -> bool {
956    let ConfiguredFeatureKind::VoidStartPlatform = kind else {
957        panic!("void_start_platform placer received wrong configured feature kind");
958    };
959    FeatureDecorationRunner::place_void_start_platform_feature(context.region, context.origin)
960}
961
962fn place_weeping_vines(
963    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
964    kind: &ConfiguredFeatureKind,
965) -> bool {
966    let ConfiguredFeatureKind::WeepingVines = kind else {
967        panic!("weeping_vines placer received wrong configured feature kind");
968    };
969    FeatureDecorationRunner::place_weeping_vines_feature(
970        context.region,
971        context.random,
972        context.origin,
973    )
974}
975
976fn place_spring_feature(
977    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
978    kind: &ConfiguredFeatureKind,
979) -> bool {
980    let ConfiguredFeatureKind::SpringFeature(config) = kind else {
981        panic!("spring_feature placer received wrong configured feature kind");
982    };
983    FeatureDecorationRunner::place_spring_feature(
984        context.region,
985        context.registry,
986        config,
987        context.origin,
988    )
989}
990
991fn place_kelp(
992    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
993    kind: &ConfiguredFeatureKind,
994) -> bool {
995    let ConfiguredFeatureKind::Kelp = kind else {
996        panic!("kelp placer received wrong configured feature kind");
997    };
998    FeatureDecorationRunner::place_kelp_feature(context.region, context.random, context.origin)
999}
1000
1001fn place_lake(
1002    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1003    kind: &ConfiguredFeatureKind,
1004) -> bool {
1005    let ConfiguredFeatureKind::Lake(config) = kind else {
1006        panic!("lake placer received wrong configured feature kind");
1007    };
1008    FeatureDecorationRunner::place_lake_feature(
1009        context.region,
1010        context.registry,
1011        context.random,
1012        config,
1013        context.origin,
1014        context.biome_zoom_seed,
1015    )
1016}
1017
1018fn place_monster_room(
1019    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1020    kind: &ConfiguredFeatureKind,
1021) -> bool {
1022    let ConfiguredFeatureKind::MonsterRoom = kind else {
1023        panic!("monster_room placer received wrong configured feature kind");
1024    };
1025    FeatureDecorationRunner::place_monster_room_feature(
1026        context.region,
1027        context.random,
1028        context.origin,
1029    )
1030}
1031
1032fn place_freeze_top_layer(
1033    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1034    kind: &ConfiguredFeatureKind,
1035) -> bool {
1036    let ConfiguredFeatureKind::FreezeTopLayer = kind else {
1037        panic!("freeze_top_layer placer received wrong configured feature kind");
1038    };
1039    FeatureDecorationRunner::place_freeze_top_layer_feature(
1040        context.region,
1041        context.registry,
1042        context.origin,
1043        context.biome_zoom_seed,
1044    )
1045}
1046
1047fn place_multiface_growth(
1048    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1049    kind: &ConfiguredFeatureKind,
1050) -> bool {
1051    let ConfiguredFeatureKind::MultifaceGrowth(config) = kind else {
1052        panic!("multiface_growth placer received wrong configured feature kind");
1053    };
1054    FeatureDecorationRunner::place_multiface_growth_feature(
1055        context.region,
1056        context.registry,
1057        context.random,
1058        config,
1059        context.origin,
1060    )
1061}
1062
1063fn place_sea_pickle(
1064    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1065    kind: &ConfiguredFeatureKind,
1066) -> bool {
1067    let ConfiguredFeatureKind::SeaPickle(config) = kind else {
1068        panic!("sea_pickle placer received wrong configured feature kind");
1069    };
1070    FeatureDecorationRunner::place_sea_pickle_feature(
1071        context.region,
1072        context.random,
1073        config,
1074        context.origin,
1075    )
1076}
1077
1078fn place_seagrass(
1079    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1080    kind: &ConfiguredFeatureKind,
1081) -> bool {
1082    let ConfiguredFeatureKind::Seagrass(config) = kind else {
1083        panic!("seagrass placer received wrong configured feature kind");
1084    };
1085    FeatureDecorationRunner::place_seagrass_feature(
1086        context.region,
1087        context.random,
1088        config,
1089        context.origin,
1090    )
1091}
1092
1093fn place_underwater_magma(
1094    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1095    kind: &ConfiguredFeatureKind,
1096) -> bool {
1097    let ConfiguredFeatureKind::UnderwaterMagma(config) = kind else {
1098        panic!("underwater_magma placer received wrong configured feature kind");
1099    };
1100    FeatureDecorationRunner::place_underwater_magma_feature(
1101        context.region,
1102        context.random,
1103        config,
1104        context.origin,
1105    )
1106}
1107
1108fn place_pointed_dripstone(
1109    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1110    kind: &ConfiguredFeatureKind,
1111) -> bool {
1112    let ConfiguredFeatureKind::PointedDripstone(config) = kind else {
1113        panic!("pointed_dripstone placer received wrong configured feature kind");
1114    };
1115    FeatureDecorationRunner::place_pointed_dripstone_feature(
1116        context.region,
1117        context.random,
1118        config,
1119        context.origin,
1120    )
1121}
1122
1123fn place_dripstone_cluster(
1124    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1125    kind: &ConfiguredFeatureKind,
1126) -> bool {
1127    let ConfiguredFeatureKind::DripstoneCluster(config) = kind else {
1128        panic!("dripstone_cluster placer received wrong configured feature kind");
1129    };
1130    FeatureDecorationRunner::place_dripstone_cluster_feature(
1131        context.region,
1132        context.random,
1133        config,
1134        context.origin,
1135    )
1136}
1137
1138fn place_speleothem(
1139    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1140    kind: &ConfiguredFeatureKind,
1141) -> bool {
1142    let ConfiguredFeatureKind::Speleothem(config) = kind else {
1143        panic!("speleothem placer received wrong configured feature kind");
1144    };
1145    FeatureDecorationRunner::place_speleothem_feature(
1146        context.region,
1147        context.random,
1148        config,
1149        context.origin,
1150    )
1151}
1152
1153fn place_speleothem_cluster(
1154    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1155    kind: &ConfiguredFeatureKind,
1156) -> bool {
1157    let ConfiguredFeatureKind::SpeleothemCluster(config) = kind else {
1158        panic!("speleothem_cluster placer received wrong configured feature kind");
1159    };
1160    FeatureDecorationRunner::place_speleothem_cluster_feature(
1161        context.region,
1162        context.random,
1163        config,
1164        context.origin,
1165    )
1166}
1167
1168fn place_large_dripstone(
1169    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1170    kind: &ConfiguredFeatureKind,
1171) -> bool {
1172    let ConfiguredFeatureKind::LargeDripstone(config) = kind else {
1173        panic!("large_dripstone placer received wrong configured feature kind");
1174    };
1175    FeatureDecorationRunner::place_large_dripstone_feature(
1176        context.region,
1177        context.random,
1178        config,
1179        context.origin,
1180    )
1181}
1182
1183fn place_spike(
1184    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1185    kind: &ConfiguredFeatureKind,
1186) -> bool {
1187    let ConfiguredFeatureKind::Spike(config) = kind else {
1188        panic!("spike placer received wrong configured feature kind");
1189    };
1190    FeatureDecorationRunner::place_spike_feature(
1191        context.region,
1192        context.registry,
1193        context.random,
1194        config,
1195        context.origin,
1196    )
1197}
1198
1199fn place_ore(
1200    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1201    kind: &ConfiguredFeatureKind,
1202) -> bool {
1203    let ConfiguredFeatureKind::Ore(config) = kind else {
1204        panic!("ore placer received wrong configured feature kind");
1205    };
1206    FeatureDecorationRunner::place_ore_feature(
1207        context.region,
1208        context.registry,
1209        context.random,
1210        config,
1211        context.origin,
1212    )
1213}
1214
1215fn place_scattered_ore(
1216    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1217    kind: &ConfiguredFeatureKind,
1218) -> bool {
1219    let ConfiguredFeatureKind::ScatteredOre(config) = kind else {
1220        panic!("scattered_ore placer received wrong configured feature kind");
1221    };
1222    FeatureDecorationRunner::place_scattered_ore_feature(
1223        context.region,
1224        context.registry,
1225        context.random,
1226        config,
1227        context.origin,
1228    )
1229}
1230
1231fn place_sculk_patch(
1232    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1233    kind: &ConfiguredFeatureKind,
1234) -> bool {
1235    let ConfiguredFeatureKind::SculkPatch(config) = kind else {
1236        panic!("sculk_patch placer received wrong configured feature kind");
1237    };
1238    FeatureDecorationRunner::place_sculk_patch_feature(
1239        context.region,
1240        context.registry,
1241        context.random,
1242        config,
1243        context.origin,
1244    )
1245}
1246
1247fn place_tree(
1248    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1249    kind: &ConfiguredFeatureKind,
1250) -> bool {
1251    let ConfiguredFeatureKind::Tree(config) = kind else {
1252        panic!("tree placer received wrong configured feature kind");
1253    };
1254    FeatureDecorationRunner::place_tree_feature(
1255        context.region,
1256        context.registry,
1257        context.random,
1258        config,
1259        context.origin,
1260        context.biome_zoom_seed,
1261        FeatureDecorationRunner::place_configured_feature_kind,
1262    )
1263}
1264
1265fn place_fallen_tree(
1266    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1267    kind: &ConfiguredFeatureKind,
1268) -> bool {
1269    let ConfiguredFeatureKind::FallenTree(config) = kind else {
1270        panic!("fallen_tree placer received wrong configured feature kind");
1271    };
1272    FeatureDecorationRunner::place_fallen_tree_feature(
1273        context.region,
1274        context.registry,
1275        context.random,
1276        config,
1277        context.origin,
1278        context.biome_zoom_seed,
1279    )
1280}
1281
1282fn place_fossil(
1283    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1284    kind: &ConfiguredFeatureKind,
1285) -> bool {
1286    let ConfiguredFeatureKind::Fossil(config) = kind else {
1287        panic!("fossil placer received wrong configured feature kind");
1288    };
1289    FeatureDecorationRunner::place_fossil_feature(
1290        context.region,
1291        context.registry,
1292        context.random,
1293        config,
1294        context.origin,
1295    )
1296}
1297
1298fn place_root_system(
1299    context: &mut ConfiguredFeaturePlaceContext<'_, '_>,
1300    kind: &ConfiguredFeatureKind,
1301) -> bool {
1302    let ConfiguredFeatureKind::RootSystem(config) = kind else {
1303        panic!("root_system placer received wrong configured feature kind");
1304    };
1305    FeatureDecorationRunner::place_root_system_feature(
1306        context.region,
1307        context.registry,
1308        context.random,
1309        config,
1310        context.origin,
1311        context.biome_zoom_seed,
1312    )
1313}
1314
1315#[cfg(test)]
1316mod tests {
1317    use super::*;
1318
1319    #[test]
1320    fn template_feature_position_uses_template_depth_for_north_offset() {
1321        assert_eq!(
1322            template_feature_position(
1323                BlockPos::new(100, 64, 200),
1324                Rotation::None,
1325                IVec3::new(10, 30, 6),
1326            ),
1327            BlockPos::new(95, 64, 197)
1328        );
1329    }
1330}