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