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