Skip to main content

steel_core/behavior/blocks/vegetation/
tall_grass_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::{
5    blocks::{
6        BlockRef,
7        block_state_ext::BlockStateExt,
8        properties::{BlockStateProperties, DoubleBlockHalf, EnumProperty},
9    },
10    vanilla_blocks,
11};
12use steel_utils::{BlockPos, BlockStateId, Direction, types::UpdateFlags};
13
14use crate::{
15    behavior::{
16        BlockBehavior, BlockPlaceContext,
17        blocks::{
18            DoublePlantBlock,
19            vegetation::{
20                Vegetation,
21                bonemealable::Bonemealable,
22                default_surviving_state,
23                vegetation_block::{survival_update_shape, vegetation_can_survive},
24            },
25        },
26    },
27    world::{LevelReader, ScheduledTickAccess, World},
28};
29
30/// Behavior for short grass and fern blocks.
31#[block_behavior]
32pub struct TallGrassBlock {
33    base: DoublePlantBlock,
34}
35
36const DOUBLE_BLOCK_HALF: &EnumProperty<DoubleBlockHalf> = &BlockStateProperties::DOUBLE_BLOCK_HALF;
37
38impl TallGrassBlock {
39    /// Creates a new tall grass behavior.
40    #[must_use]
41    pub const fn new(block: BlockRef) -> Self {
42        Self {
43            base: DoublePlantBlock::new(block),
44        }
45    }
46
47    fn large_variant(state: BlockStateId) -> BlockRef {
48        if state.get_block() == &vanilla_blocks::FERN {
49            &vanilla_blocks::LARGE_FERN
50        } else {
51            &vanilla_blocks::TALL_GRASS
52        }
53    }
54}
55
56impl BlockBehavior for TallGrassBlock {
57    fn update_shape(
58        &self,
59        state: BlockStateId,
60        world: &dyn ScheduledTickAccess,
61        pos: BlockPos,
62        _direction: Direction,
63        _neighbor_pos: BlockPos,
64        _neighbor_state: BlockStateId,
65    ) -> BlockStateId {
66        survival_update_shape(self, state, world, pos)
67    }
68
69    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
70        vegetation_can_survive(self, state, world, pos)
71    }
72
73    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
74        default_surviving_state(self.base.block, self, context)
75    }
76
77    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
78        Some(self)
79    }
80}
81
82impl Vegetation for TallGrassBlock {}
83
84impl Bonemealable for TallGrassBlock {
85    fn is_valid_bonemeal_target(
86        &self,
87        state: BlockStateId,
88        world: &dyn LevelReader,
89        pos: BlockPos,
90    ) -> bool {
91        let above_pos = pos.above();
92        let lower_state = Self::large_variant(state)
93            .default_state()
94            .set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Lower);
95        !world.is_outside_build_height(above_pos.y())
96            && self.base.can_survive(lower_state, world, pos)
97            && world.get_block_state(above_pos).is_air()
98    }
99
100    fn perform_bonemeal(
101        &self,
102        state: BlockStateId,
103        world: &Arc<World>,
104        _rng: &mut dyn rand::Rng,
105        pos: BlockPos,
106    ) {
107        DoublePlantBlock::place_at(
108            world,
109            Self::large_variant(state).default_state(),
110            pos,
111            UpdateFlags::UPDATE_CLIENTS,
112        );
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use steel_registry::init_vanilla_registry;
119
120    use crate::test_support::TestLevel;
121
122    use super::*;
123
124    #[test]
125    fn tall_grass_bonemeal_rejects_top_build_height() {
126        init_vanilla_registry();
127        let behavior = TallGrassBlock::new(&vanilla_blocks::SHORT_GRASS);
128        let state = vanilla_blocks::SHORT_GRASS.default_state();
129        let level = TestLevel::default()
130            .with_min_y(0)
131            .with_height(1)
132            .with_block(BlockPos::ZERO.below(), vanilla_blocks::DIRT.default_state());
133
134        assert!(!behavior.is_valid_bonemeal_target(state, &level, BlockPos::ZERO));
135    }
136}