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