Skip to main content

steel_core/worldgen/feature/features/tree/
decorators.rs

1#![expect(
2    clippy::too_many_arguments,
3    reason = "tree decorators mirror vanilla decorator context"
4)]
5
6use steel_registry::vanilla_block_tags::BlockTag;
7use steel_utils::Downcast as _;
8
9use super::super::super::prelude::*;
10use super::super::super::runner::FeatureDecorationRunner;
11use super::{NestedFeaturePlacer, TreePlacement};
12use crate::java_block_pos_set::JavaBlockPosSet;
13
14use crate::block_entity::entities::BeehiveBlockEntity;
15
16const BEEHIVE_WORLDGEN_FACING: Direction = Direction::South;
17const BEEHIVE_SPAWN_DIRECTIONS: [Direction; 3] =
18    [Direction::East, Direction::South, Direction::West];
19const COCOA_PER_SIDE_PLACEMENT_CHANCE: f32 = 0.25;
20const SHELF_MUSHROOM_PER_SIDE_PLACEMENT_CHANCE: f32 = 0.25;
21
22impl FeatureDecorationRunner {
23    pub(super) fn place_tree_decorators<L: LevelAccessor>(
24        region: &mut L,
25        registry: &Registry,
26        random: &mut WorldgenRandom,
27        decorators: &[TreeDecorator],
28        placement: &mut TreePlacement,
29        biome_zoom_seed: i64,
30        place_nested: NestedFeaturePlacer<L>,
31    ) {
32        for decorator in decorators {
33            match decorator {
34                TreeDecorator::AlterGround { provider } => {
35                    Self::place_alter_ground_tree_decorator(
36                        region, registry, random, provider, placement,
37                    );
38                }
39                TreeDecorator::Beehive { probability } => {
40                    Self::place_beehive_tree_decorator(
41                        region,
42                        registry,
43                        random,
44                        *probability,
45                        placement,
46                    );
47                }
48                TreeDecorator::Cocoa { probability } => {
49                    Self::place_cocoa_tree_decorator(
50                        region,
51                        registry,
52                        random,
53                        *probability,
54                        placement,
55                    );
56                }
57                TreeDecorator::LeaveVine { probability } => {
58                    Self::place_leave_vine_tree_decorator(region, random, *probability, placement);
59                }
60                TreeDecorator::TrunkVine => {
61                    Self::place_trunk_vine_tree_decorator(region, random, placement);
62                }
63                TreeDecorator::PlaceOnGround(decorator) => {
64                    Self::place_on_ground_tree_decorator(
65                        region, registry, random, decorator, placement,
66                    );
67                }
68                TreeDecorator::AttachedToLeaves(decorator) => {
69                    Self::place_attached_to_leaves_tree_decorator(
70                        region, registry, random, decorator, placement,
71                    );
72                }
73                TreeDecorator::AttachedToLogs(decorator) => {
74                    Self::place_attached_to_logs_tree_decorator(
75                        region, registry, random, decorator, placement,
76                    );
77                }
78                TreeDecorator::PaleMoss {
79                    leaves_probability,
80                    trunk_probability,
81                    ground_probability,
82                } => {
83                    Self::place_pale_moss_tree_decorator(
84                        region,
85                        registry,
86                        random,
87                        *leaves_probability,
88                        *trunk_probability,
89                        *ground_probability,
90                        placement,
91                        biome_zoom_seed,
92                        place_nested,
93                    );
94                }
95                TreeDecorator::CreakingHeart { probability } => {
96                    Self::place_creaking_heart_tree_decorator(
97                        region,
98                        registry,
99                        random,
100                        *probability,
101                        placement,
102                    );
103                }
104                TreeDecorator::ShelfMushroom { probability } => {
105                    Self::place_shelf_mushroom_tree_decorator(
106                        region,
107                        registry,
108                        random,
109                        *probability,
110                        placement,
111                    );
112                }
113            }
114        }
115    }
116
117    fn place_alter_ground_tree_decorator(
118        region: &mut impl LevelAccessor,
119        registry: &Registry,
120        random: &mut WorldgenRandom,
121        provider: &BlockStateProviderKind,
122        placement: &mut TreePlacement,
123    ) {
124        let positions = Self::lowest_tree_trunks_or_roots(placement);
125        let Some(first_pos) = positions.first() else {
126            return;
127        };
128        let min_y = first_pos.y();
129
130        for pos in positions.into_iter().filter(|pos| pos.y() == min_y) {
131            Self::place_alter_ground_circle(
132                region,
133                registry,
134                random,
135                provider,
136                pos.offset(-1, 0, -1),
137                placement,
138            );
139            Self::place_alter_ground_circle(
140                region,
141                registry,
142                random,
143                provider,
144                pos.offset(2, 0, -1),
145                placement,
146            );
147            Self::place_alter_ground_circle(
148                region,
149                registry,
150                random,
151                provider,
152                pos.offset(-1, 0, 2),
153                placement,
154            );
155            Self::place_alter_ground_circle(
156                region,
157                registry,
158                random,
159                provider,
160                pos.offset(2, 0, 2),
161                placement,
162            );
163
164            for _ in 0..5 {
165                let placement_offset = random.next_i32_bounded(64);
166                let x = placement_offset % 8;
167                let z = placement_offset / 8;
168                if x == 0 || x == 7 || z == 0 || z == 7 {
169                    Self::place_alter_ground_circle(
170                        region,
171                        registry,
172                        random,
173                        provider,
174                        pos.offset(-3 + x, 0, -3 + z),
175                        placement,
176                    );
177                }
178            }
179        }
180    }
181
182    fn place_alter_ground_circle(
183        region: &mut impl LevelAccessor,
184        registry: &Registry,
185        random: &mut WorldgenRandom,
186        provider: &BlockStateProviderKind,
187        pos: BlockPos,
188        placement: &mut TreePlacement,
189    ) {
190        for x in -2i32..=2 {
191            for z in -2i32..=2 {
192                if x.abs() != 2 || z.abs() != 2 {
193                    Self::place_alter_ground_block_at(
194                        region,
195                        registry,
196                        random,
197                        provider,
198                        pos.offset(x, 0, z),
199                        placement,
200                    );
201                }
202            }
203        }
204    }
205
206    fn place_alter_ground_block_at(
207        region: &mut impl LevelAccessor,
208        registry: &Registry,
209        random: &mut WorldgenRandom,
210        provider: &BlockStateProviderKind,
211        pos: BlockPos,
212        placement: &mut TreePlacement,
213    ) {
214        for y in (-3..=2).rev() {
215            let cursor = pos.above_n(y);
216            if let Some(state) = BlockStateProviderEvaluator::sample_block_state_provider_optional(
217                region, registry, random, provider, cursor,
218            ) {
219                placement.set_decoration(region, cursor, state);
220                break;
221            }
222
223            if !region.get_block_state(cursor).is_air() && y < 0 {
224                break;
225            }
226        }
227    }
228
229    fn place_on_ground_tree_decorator(
230        region: &mut impl LevelAccessor,
231        registry: &Registry,
232        random: &mut WorldgenRandom,
233        decorator: &PlaceOnGroundDecorator,
234        placement: &mut TreePlacement,
235    ) {
236        let positions = Self::lowest_tree_trunks_or_roots(placement);
237        let Some(origin) = positions.first() else {
238            return;
239        };
240        let min_y = origin.y();
241        let mut min_x = origin.x();
242        let mut max_x = origin.x();
243        let mut min_z = origin.z();
244        let mut max_z = origin.z();
245
246        for position in positions {
247            if position.y() == min_y {
248                min_x = min_x.min(position.x());
249                max_x = max_x.max(position.x());
250                min_z = min_z.min(position.z());
251                max_z = max_z.max(position.z());
252            }
253        }
254
255        min_x -= decorator.radius;
256        max_x += decorator.radius;
257        min_z -= decorator.radius;
258        max_z += decorator.radius;
259        let min_y = min_y - decorator.height;
260        let max_y = min_y + decorator.height * 2;
261        for _ in 0..decorator.tries {
262            let pos = BlockPos::new(
263                random.next_i32_between(min_x, max_x),
264                random.next_i32_between(min_y, max_y),
265                random.next_i32_between(min_z, max_z),
266            );
267            Self::attempt_place_tree_ground_decorator(
268                region,
269                registry,
270                random,
271                &decorator.block_state_provider,
272                pos,
273                placement,
274            );
275        }
276    }
277
278    fn attempt_place_tree_ground_decorator(
279        region: &mut impl LevelAccessor,
280        registry: &Registry,
281        random: &mut WorldgenRandom,
282        provider: &BlockStateProviderKind,
283        pos: BlockPos,
284        placement: &mut TreePlacement,
285    ) {
286        let above = pos.above();
287        let above_state = region.get_block_state(above);
288        if !above_state.is_air() && above_state.get_block() != &vanilla_blocks::VINE {
289            return;
290        }
291        if !region.get_block_state(pos).is_solid_render() {
292            return;
293        }
294        if region.height_at(HeightmapType::MotionBlockingNoLeaves, pos.x(), pos.z()) > above.y() {
295            return;
296        }
297
298        let state = BlockStateProviderEvaluator::sample_block_state_provider(
299            region, registry, random, provider, above,
300        );
301        placement.set_decoration(region, above, state);
302    }
303
304    fn place_trunk_vine_tree_decorator(
305        region: &mut impl LevelAccessor,
306        random: &mut WorldgenRandom,
307        placement: &mut TreePlacement,
308    ) {
309        for log in Self::sorted_tree_positions(&placement.trunks) {
310            if random.next_i32_bounded(3) > 0 {
311                Self::try_place_tree_vine(
312                    region,
313                    placement,
314                    log.relative(Direction::West),
315                    Direction::East,
316                );
317            }
318            if random.next_i32_bounded(3) > 0 {
319                Self::try_place_tree_vine(
320                    region,
321                    placement,
322                    log.relative(Direction::East),
323                    Direction::West,
324                );
325            }
326            if random.next_i32_bounded(3) > 0 {
327                Self::try_place_tree_vine(
328                    region,
329                    placement,
330                    log.relative(Direction::North),
331                    Direction::South,
332                );
333            }
334            if random.next_i32_bounded(3) > 0 {
335                Self::try_place_tree_vine(
336                    region,
337                    placement,
338                    log.relative(Direction::South),
339                    Direction::North,
340                );
341            }
342        }
343    }
344
345    fn place_leave_vine_tree_decorator(
346        region: &mut impl LevelAccessor,
347        random: &mut WorldgenRandom,
348        probability: f32,
349        placement: &mut TreePlacement,
350    ) {
351        for leaf in Self::sorted_tree_positions(&placement.foliage) {
352            if random.next_f32() < probability {
353                Self::try_place_hanging_tree_vine(
354                    region,
355                    placement,
356                    leaf.relative(Direction::West),
357                    Direction::East,
358                );
359            }
360            if random.next_f32() < probability {
361                Self::try_place_hanging_tree_vine(
362                    region,
363                    placement,
364                    leaf.relative(Direction::East),
365                    Direction::West,
366                );
367            }
368            if random.next_f32() < probability {
369                Self::try_place_hanging_tree_vine(
370                    region,
371                    placement,
372                    leaf.relative(Direction::North),
373                    Direction::South,
374                );
375            }
376            if random.next_f32() < probability {
377                Self::try_place_hanging_tree_vine(
378                    region,
379                    placement,
380                    leaf.relative(Direction::South),
381                    Direction::North,
382                );
383            }
384        }
385    }
386
387    fn try_place_hanging_tree_vine(
388        region: &mut impl LevelAccessor,
389        placement: &mut TreePlacement,
390        pos: BlockPos,
391        vine_face: Direction,
392    ) {
393        if !Self::try_place_tree_vine(region, placement, pos, vine_face) {
394            return;
395        }
396
397        let mut pos = pos.below();
398        let mut max_length = 4;
399        while region.get_block_state(pos).is_air() && max_length > 0 {
400            Self::place_tree_vine(region, placement, pos, vine_face);
401            pos = pos.below();
402            max_length -= 1;
403        }
404    }
405
406    fn place_cocoa_tree_decorator(
407        region: &mut impl LevelAccessor,
408        registry: &Registry,
409        random: &mut WorldgenRandom,
410        probability: f32,
411        placement: &mut TreePlacement,
412    ) {
413        if random.next_f32() >= probability {
414            return;
415        }
416
417        let logs = Self::sorted_tree_positions(&placement.trunks);
418        let Some(first_log) = logs.first() else {
419            return;
420        };
421        let tree_y = first_log.y();
422
423        for log in logs.into_iter().filter(|pos| pos.y() - tree_y <= 2) {
424            for direction in Self::VANILLA_HORIZONTAL_DIRECTIONS {
425                if random.next_f32() > COCOA_PER_SIDE_PLACEMENT_CHANCE {
426                    continue;
427                }
428
429                let cocoa_pos = log.relative(direction.opposite());
430                if !region.get_block_state(cocoa_pos).is_air() {
431                    continue;
432                }
433
434                let age = random.next_i32_bounded(3) as u8;
435                let cocoa_state = registry
436                    .blocks
437                    .get_default_state_id(&vanilla_blocks::COCOA)
438                    .set_value(&BlockStateProperties::AGE_2, age)
439                    .set_value(&BlockStateProperties::HORIZONTAL_FACING, direction);
440                placement.set_decoration(region, cocoa_pos, cocoa_state);
441            }
442        }
443    }
444
445    fn place_shelf_mushroom_tree_decorator(
446        region: &mut impl LevelAccessor,
447        registry: &Registry,
448        random: &mut WorldgenRandom,
449        probability: f32,
450        placement: &mut TreePlacement,
451    ) {
452        if random.next_f32() >= probability {
453            return;
454        }
455
456        let logs = Self::sorted_tree_positions(&placement.trunks);
457        let Some(first_log) = logs.first() else {
458            return;
459        };
460        let Some(last_log) = logs.last() else {
461            return;
462        };
463
464        if first_log.y() == last_log.y() {
465            Self::place_shelf_mushrooms_on_fallen_log(region, registry, random, &logs, placement);
466        } else {
467            Self::place_shelf_mushrooms_on_standing_tree(
468                region, registry, random, &logs, placement,
469            );
470        }
471    }
472
473    fn place_shelf_mushrooms_on_standing_tree(
474        region: &mut impl LevelAccessor,
475        registry: &Registry,
476        random: &mut WorldgenRandom,
477        logs: &[BlockPos],
478        placement: &mut TreePlacement,
479    ) {
480        let first_direction = Self::random_horizontal_direction(random);
481        let directions = [first_direction, first_direction.rotate_y_clockwise()];
482        let tree_base_y = logs[0].y();
483
484        for &log_pos in logs {
485            let height = log_pos.y() - tree_base_y;
486            if !(1..=4).contains(&height) {
487                continue;
488            }
489
490            for facing in directions {
491                if random.next_f32() <= SHELF_MUSHROOM_PER_SIDE_PLACEMENT_CHANCE
492                    && Self::try_place_shelf_mushroom_on_standing_tree(
493                        region, registry, random, log_pos, facing, placement,
494                    )
495                {
496                    break;
497                }
498            }
499        }
500    }
501
502    fn place_shelf_mushrooms_on_fallen_log(
503        region: &mut impl LevelAccessor,
504        registry: &Registry,
505        random: &mut WorldgenRandom,
506        logs: &[BlockPos],
507        placement: &mut TreePlacement,
508    ) {
509        let Some(last_log) = logs.last() else {
510            return;
511        };
512        let directions = if logs[0].x() == last_log.x() {
513            [Direction::East, Direction::West]
514        } else {
515            [Direction::North, Direction::South]
516        };
517
518        for &log_pos in logs {
519            for facing in directions {
520                if random.next_f32() <= SHELF_MUSHROOM_PER_SIDE_PLACEMENT_CHANCE {
521                    Self::try_place_shelf_mushroom_on_fallen_log(
522                        region, registry, random, log_pos, facing, placement,
523                    );
524                }
525            }
526        }
527    }
528
529    fn try_place_shelf_mushroom_on_standing_tree(
530        region: &mut impl LevelAccessor,
531        registry: &Registry,
532        random: &mut WorldgenRandom,
533        log_pos: BlockPos,
534        facing: Direction,
535        placement: &mut TreePlacement,
536    ) -> bool {
537        let mushroom_pos = log_pos.relative(facing);
538        if !Self::is_replaceable_with_shelf_mushroom(region, mushroom_pos)
539            || Self::has_shelf_mushroom_at(region, mushroom_pos.below())
540        {
541            return false;
542        }
543
544        Self::place_shelf_mushroom(region, registry, random, mushroom_pos, facing, placement);
545        true
546    }
547
548    fn try_place_shelf_mushroom_on_fallen_log(
549        region: &mut impl LevelAccessor,
550        registry: &Registry,
551        random: &mut WorldgenRandom,
552        log_pos: BlockPos,
553        facing: Direction,
554        placement: &mut TreePlacement,
555    ) {
556        let mushroom_pos = log_pos.relative(facing);
557        if !Self::is_replaceable_with_shelf_mushroom(region, mushroom_pos)
558            || Self::has_horizontally_adjacent_shelf_mushroom(region, mushroom_pos)
559            || Self::has_horizontally_adjacent_shelf_mushroom(region, log_pos)
560        {
561            return;
562        }
563
564        Self::place_shelf_mushroom(region, registry, random, mushroom_pos, facing, placement);
565    }
566
567    fn place_shelf_mushroom(
568        region: &mut impl LevelAccessor,
569        registry: &Registry,
570        random: &mut WorldgenRandom,
571        pos: BlockPos,
572        facing: Direction,
573        placement: &mut TreePlacement,
574    ) {
575        let state = registry
576            .blocks
577            .get_default_state_id(&vanilla_blocks::SHELF_MUSHROOM)
578            .set_value(
579                &BlockStateProperties::AGE_1,
580                random.next_i32_bounded(2) as u8,
581            )
582            .set_value(&BlockStateProperties::HORIZONTAL_FACING, facing);
583        placement.set_decoration(region, pos, state);
584    }
585
586    fn is_replaceable_with_shelf_mushroom(region: &impl LevelReader, pos: BlockPos) -> bool {
587        region.get_block_state(pos).is_replaceable() && !Self::is_water_or_water_nearby(region, pos)
588    }
589
590    fn is_water_or_water_nearby(region: &impl LevelReader, pos: BlockPos) -> bool {
591        [
592            Direction::North,
593            Direction::East,
594            Direction::South,
595            Direction::West,
596        ]
597        .into_iter()
598        .any(|direction| {
599            region.get_block_state(pos.relative(direction)).get_block() == &vanilla_blocks::WATER
600        }) || region.get_block_state(pos).get_block() == &vanilla_blocks::WATER
601    }
602
603    fn has_shelf_mushroom_at(region: &impl LevelReader, pos: BlockPos) -> bool {
604        region.get_block_state(pos).get_block() == &vanilla_blocks::SHELF_MUSHROOM
605    }
606
607    fn has_horizontally_adjacent_shelf_mushroom(region: &impl LevelReader, pos: BlockPos) -> bool {
608        Self::VANILLA_HORIZONTAL_DIRECTIONS
609            .into_iter()
610            .any(|direction| Self::has_shelf_mushroom_at(region, pos.relative(direction)))
611    }
612
613    fn try_place_tree_vine(
614        region: &mut impl LevelAccessor,
615        placement: &mut TreePlacement,
616        pos: BlockPos,
617        vine_face: Direction,
618    ) -> bool {
619        if !region.get_block_state(pos).is_air() {
620            return false;
621        }
622
623        Self::place_tree_vine(region, placement, pos, vine_face);
624        true
625    }
626
627    fn place_tree_vine(
628        region: &mut impl LevelAccessor,
629        placement: &mut TreePlacement,
630        pos: BlockPos,
631        vine_face: Direction,
632    ) {
633        placement.set_decoration(region, pos, Self::vine_state_for_face(vine_face));
634    }
635
636    fn lowest_tree_trunks_or_roots(placement: &TreePlacement) -> Vec<BlockPos> {
637        let roots = Self::sorted_tree_positions(&placement.roots);
638        let logs = Self::sorted_tree_positions(&placement.trunks);
639        if roots.is_empty() {
640            return logs;
641        }
642
643        if logs.first().is_some_and(|log| roots[0].y() == log.y()) {
644            let mut positions = logs;
645            positions.extend(roots);
646            positions
647        } else {
648            roots
649        }
650    }
651
652    fn place_beehive_tree_decorator(
653        region: &mut impl LevelAccessor,
654        registry: &Registry,
655        random: &mut WorldgenRandom,
656        probability: f32,
657        placement: &mut TreePlacement,
658    ) {
659        let logs = Self::sorted_tree_positions(&placement.trunks);
660        if logs.is_empty() || random.next_f32() >= probability {
661            return;
662        }
663
664        let leaves = Self::sorted_tree_positions(&placement.foliage);
665        let hive_y = if let Some(first_leaf) = leaves.first() {
666            (first_leaf.y() - 1).max(logs[0].y() + 1)
667        } else {
668            let log_y = logs[0].y() + 1 + random.next_i32_bounded(3);
669            let last_log_y = logs[logs.len() - 1].y();
670            log_y.min(last_log_y)
671        };
672
673        let mut hive_placements = Vec::new();
674        for log in logs.iter().copied().filter(|pos| pos.y() == hive_y) {
675            for direction in BEEHIVE_SPAWN_DIRECTIONS {
676                hive_placements.push(log.relative(direction));
677            }
678        }
679
680        if hive_placements.is_empty() {
681            return;
682        }
683
684        Self::shuffle_tree_positions(random, &mut hive_placements);
685        let hive_pos = hive_placements.into_iter().find(|pos| {
686            region.get_block_state(*pos).is_air()
687                && region
688                    .get_block_state(pos.relative(BEEHIVE_WORLDGEN_FACING))
689                    .is_air()
690        });
691        let Some(hive_pos) = hive_pos else {
692            return;
693        };
694
695        let hive_state = registry
696            .blocks
697            .get_default_state_id(&vanilla_blocks::BEE_NEST)
698            .set_value(
699                &BlockStateProperties::HORIZONTAL_FACING,
700                BEEHIVE_WORLDGEN_FACING,
701            );
702        placement.set_decoration(region, hive_pos, hive_state);
703
704        let Some(block_entity) = region.get_block_entity(hive_pos) else {
705            return;
706        };
707        let Some(beehive) = block_entity.downcast_ref::<BeehiveBlockEntity>() else {
708            return;
709        };
710
711        let num_bees = 2 + random.next_i32_bounded(2);
712        for _ in 0..num_bees {
713            beehive.store_worldgen_bee(random.next_i32_bounded(599));
714        }
715    }
716
717    fn place_attached_to_leaves_tree_decorator(
718        region: &mut impl LevelAccessor,
719        registry: &Registry,
720        random: &mut WorldgenRandom,
721        decorator: &AttachedToLeavesDecorator,
722        placement: &mut TreePlacement,
723    ) {
724        let mut blacklist = FxHashSet::default();
725        let mut leaves = Self::sorted_tree_positions(&placement.foliage);
726        Self::shuffle_tree_positions(random, &mut leaves);
727
728        for leaf in leaves {
729            let direction = Self::random_tree_decorator_direction(random, &decorator.directions);
730            let place_pos = leaf.relative(direction);
731            if blacklist.contains(&place_pos)
732                || random.next_f32() >= decorator.probability
733                || !Self::tree_decorator_has_required_empty_blocks(
734                    region,
735                    leaf,
736                    direction,
737                    decorator.required_empty_blocks,
738                )
739            {
740                continue;
741            }
742
743            Self::blacklist_attached_tree_decoration_area(
744                &mut blacklist,
745                place_pos,
746                decorator.exclusion_radius_xz,
747                decorator.exclusion_radius_y,
748            );
749            let state = BlockStateProviderEvaluator::sample_block_state_provider(
750                region,
751                registry,
752                random,
753                &decorator.block_provider,
754                place_pos,
755            );
756            placement.set_decoration(region, place_pos, state);
757        }
758    }
759
760    fn place_attached_to_logs_tree_decorator(
761        region: &mut impl LevelAccessor,
762        registry: &Registry,
763        random: &mut WorldgenRandom,
764        decorator: &AttachedToLogsDecorator,
765        placement: &mut TreePlacement,
766    ) {
767        let mut logs = Self::sorted_tree_positions(&placement.trunks);
768        Self::shuffle_tree_positions(random, &mut logs);
769
770        for log in logs {
771            let direction = Self::random_tree_decorator_direction(random, &decorator.directions);
772            let place_pos = log.relative(direction);
773            if random.next_f32() > decorator.probability
774                || !region.get_block_state(place_pos).is_air()
775            {
776                continue;
777            }
778
779            let state = BlockStateProviderEvaluator::sample_block_state_provider(
780                region,
781                registry,
782                random,
783                &decorator.block_provider,
784                place_pos,
785            );
786            placement.set_decoration(region, place_pos, state);
787        }
788    }
789
790    fn place_pale_moss_tree_decorator<L: LevelAccessor>(
791        region: &mut L,
792        registry: &Registry,
793        random: &mut WorldgenRandom,
794        leaves_probability: f32,
795        trunk_probability: f32,
796        ground_probability: f32,
797        placement: &mut TreePlacement,
798        biome_zoom_seed: i64,
799        place_nested: NestedFeaturePlacer<L>,
800    ) {
801        let mut shuffled_logs = Self::sorted_tree_positions(&placement.trunks);
802        Self::shuffle_tree_positions(random, &mut shuffled_logs);
803        let Some(origin) = shuffled_logs.into_iter().min_by_key(BlockPos::y) else {
804            return;
805        };
806
807        if random.next_f32() < ground_probability {
808            let pale_moss_patch_key = Identifier::vanilla_static("pale_moss_patch");
809            let Some(pale_moss_patch) = registry.configured_features.by_key(&pale_moss_patch_key)
810            else {
811                panic!(
812                    "pale moss tree decorator references unknown configured feature {pale_moss_patch_key}"
813                );
814            };
815            place_nested(
816                region,
817                registry,
818                random,
819                &pale_moss_patch.kind,
820                origin.above(),
821                biome_zoom_seed,
822            );
823        }
824
825        for log in Self::sorted_tree_positions(&placement.trunks) {
826            if random.next_f32() < trunk_probability {
827                let down = log.below();
828                if region.get_block_state(down).is_air() {
829                    Self::add_pale_moss_hanger(region, random, down, placement);
830                }
831            }
832        }
833
834        for leaf in Self::sorted_tree_positions(&placement.foliage) {
835            if random.next_f32() < leaves_probability {
836                let down = leaf.below();
837                if region.get_block_state(down).is_air() {
838                    Self::add_pale_moss_hanger(region, random, down, placement);
839                }
840            }
841        }
842    }
843
844    fn place_creaking_heart_tree_decorator(
845        region: &mut impl LevelAccessor,
846        registry: &Registry,
847        random: &mut WorldgenRandom,
848        probability: f32,
849        placement: &mut TreePlacement,
850    ) {
851        if placement.trunks.is_empty() || random.next_f32() >= probability {
852            return;
853        }
854
855        let mut heart_placements = Self::sorted_tree_positions(&placement.trunks);
856        Self::shuffle_tree_positions(random, &mut heart_placements);
857        let Some(target_pos) = heart_placements.into_iter().find(|pos| {
858            Self::VANILLA_DIRECTION_VALUES.iter().all(|direction| {
859                region
860                    .get_block_state(pos.relative(*direction))
861                    .get_block()
862                    .has_tag(&BlockTag::LOGS)
863            })
864        }) else {
865            return;
866        };
867
868        let state = registry
869            .blocks
870            .get_default_state_id(&vanilla_blocks::CREAKING_HEART)
871            .set_value(
872                &BlockStateProperties::CREAKING_HEART_STATE,
873                CreakingHeartState::Dormant,
874            )
875            .set_value(&BlockStateProperties::NATURAL, true);
876        placement.set_decoration(region, target_pos, state);
877    }
878
879    fn add_pale_moss_hanger(
880        region: &mut impl LevelAccessor,
881        random: &mut WorldgenRandom,
882        mut pos: BlockPos,
883        placement: &mut TreePlacement,
884    ) {
885        while region.get_block_state(pos.below()).is_air() {
886            if random.next_f32() < 0.5 {
887                break;
888            }
889
890            let state = vanilla_blocks::PALE_HANGING_MOSS
891                .default_state()
892                .set_value(&BlockStateProperties::TIP, false);
893            placement.set_decoration(region, pos, state);
894            pos = pos.below();
895        }
896
897        let state = vanilla_blocks::PALE_HANGING_MOSS
898            .default_state()
899            .set_value(&BlockStateProperties::TIP, true);
900        placement.set_decoration(region, pos, state);
901    }
902
903    fn tree_decorator_has_required_empty_blocks(
904        region: &mut impl LevelAccessor,
905        leaf: BlockPos,
906        direction: Direction,
907        required_empty_blocks: i32,
908    ) -> bool {
909        (1..=required_empty_blocks).all(|offset| {
910            region
911                .get_block_state(leaf.relative_n(direction, offset))
912                .is_air()
913        })
914    }
915
916    fn blacklist_attached_tree_decoration_area(
917        blacklist: &mut FxHashSet<BlockPos>,
918        center: BlockPos,
919        radius_xz: i32,
920        radius_y: i32,
921    ) {
922        for x in -radius_xz..=radius_xz {
923            for y in -radius_y..=radius_y {
924                for z in -radius_xz..=radius_xz {
925                    blacklist.insert(center.offset(x, y, z));
926                }
927            }
928        }
929    }
930
931    fn random_tree_decorator_direction(
932        random: &mut WorldgenRandom,
933        directions: &[Direction],
934    ) -> Direction {
935        assert!(
936            !directions.is_empty(),
937            "attached tree decorator direction list must not be empty"
938        );
939        let Ok(direction_count) = i32::try_from(directions.len()) else {
940            panic!("attached tree decorator direction count exceeds i32 range");
941        };
942        directions[random.next_i32_bounded(direction_count) as usize]
943    }
944
945    fn sorted_tree_positions(positions: &JavaBlockPosSet) -> Vec<BlockPos> {
946        let mut positions = positions.java_ordered_positions();
947        positions.sort_by_key(BlockPos::y);
948        positions
949    }
950
951    fn shuffle_tree_positions(random: &mut WorldgenRandom, positions: &mut [BlockPos]) {
952        for i in (1..positions.len()).rev() {
953            let Ok(bound) = i32::try_from(i + 1) else {
954                panic!("tree decorator shuffle length exceeds i32 range");
955            };
956            let j = random.next_i32_bounded(bound) as usize;
957            positions.swap(i, j);
958        }
959    }
960}