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}