steel_core/behavior/blocks/vegetation/
rooted_dirt_block.rs1use std::sync::Arc;
2
3use rand::Rng;
4use steel_macros::block_behavior;
5use steel_registry::{
6 blocks::{BlockRef, block_state_ext::BlockStateExt},
7 vanilla_blocks,
8};
9use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
10
11use crate::{
12 behavior::{BlockBehavior, BlockPlaceContext, blocks::vegetation::bonemealable::Bonemealable},
13 world::{LevelReader, World},
14};
15
16#[block_behavior]
18pub struct RootedDirtBlock {
19 block: BlockRef,
20}
21
22impl RootedDirtBlock {
23 #[must_use]
25 pub const fn new(block: BlockRef) -> Self {
26 Self { block }
27 }
28}
29
30impl BlockBehavior for RootedDirtBlock {
31 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
32 Some(self.block.default_state())
33 }
34
35 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
36 Some(self)
37 }
38}
39impl Bonemealable for RootedDirtBlock {
40 fn is_valid_bonemeal_target(
41 &self,
42 _state: BlockStateId,
43 world: &dyn LevelReader,
44 pos: BlockPos,
45 ) -> bool {
46 let below_pos = pos.below();
47 !world.is_outside_build_height(below_pos.y()) && world.get_block_state(below_pos).is_air()
48 }
49
50 fn perform_bonemeal(
51 &self,
52 _state: BlockStateId,
53 world: &Arc<World>,
54 _rng: &mut dyn Rng,
55 pos: BlockPos,
56 ) {
57 world.set_block(
58 pos.below(),
59 vanilla_blocks::HANGING_ROOTS.default_state(),
60 UpdateFlags::UPDATE_ALL,
61 );
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use steel_registry::init_vanilla_registry;
68
69 use crate::test_support::TestLevel;
70
71 use super::*;
72
73 fn rooted_dirt_level(min_y: i32, height: i32, below: BlockStateId) -> TestLevel {
74 TestLevel::default()
75 .with_min_y(min_y)
76 .with_height(height)
77 .with_block(BlockPos::ZERO.below(), below)
78 }
79
80 #[test]
81 fn rooted_dirt_bonemeal_rejects_bottom_build_height() {
82 init_vanilla_registry();
83 let behavior = RootedDirtBlock::new(&vanilla_blocks::ROOTED_DIRT);
84 let state = vanilla_blocks::ROOTED_DIRT.default_state();
85 let level = rooted_dirt_level(0, 1, vanilla_blocks::AIR.default_state());
86
87 assert!(!behavior.is_valid_bonemeal_target(state, &level, BlockPos::ZERO));
88 }
89
90 #[test]
91 fn rooted_dirt_bonemeal_accepts_in_bounds_air_below() {
92 init_vanilla_registry();
93 let behavior = RootedDirtBlock::new(&vanilla_blocks::ROOTED_DIRT);
94 let state = vanilla_blocks::ROOTED_DIRT.default_state();
95 let level = rooted_dirt_level(-1, 2, vanilla_blocks::AIR.default_state());
96
97 assert!(behavior.is_valid_bonemeal_target(state, &level, BlockPos::ZERO));
98 }
99}