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