Skip to main content

steel_core/behavior/blocks/vegetation/
rooted_dirt_block.rs

1use 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/// Vanilla `RootedDirtBlock` bonemeal behavior
17#[block_behavior]
18pub struct RootedDirtBlock {
19    block: BlockRef,
20}
21
22impl RootedDirtBlock {
23    /// Creates a new rooted dirt block
24    #[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}