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}