Skip to main content

steel_core/behavior/blocks/vegetation/
tall_seagrass_block.rs

1use steel_macros::block_behavior;
2use steel_registry::blocks::block_state_ext::BlockStateExt;
3use steel_registry::blocks::properties::{
4    BlockStateProperties, Direction, DoubleBlockHalf, EnumProperty,
5};
6use steel_registry::fluid::{FluidRef, FluidState, FluidStateExt as _};
7use steel_registry::item_stack::ItemStack;
8use steel_registry::vanilla_block_tags::BlockTag;
9use steel_registry::vanilla_blocks;
10use steel_registry::vanilla_items;
11use steel_utils::axis::Axis;
12use steel_utils::{BlockPos, BlockStateId};
13
14use crate::behavior::block::BlockBehavior;
15use crate::behavior::context::BlockPlaceContext;
16use crate::block_entity::SharedBlockEntity;
17use crate::fluid::get_fluid_state_from_block;
18use crate::world::{LevelAccessor, LevelReader, ScheduledTickAccess};
19
20use super::{BlockRef, water_source_fluid_state};
21
22/// Behavior for tall seagrass blocks.
23#[block_behavior]
24pub struct TallSeagrassBlock {
25    block: BlockRef,
26}
27
28const DOUBLE_BLOCK_HALF: &EnumProperty<DoubleBlockHalf> = &BlockStateProperties::DOUBLE_BLOCK_HALF;
29
30impl TallSeagrassBlock {
31    /// Creates a new tall seagrass block behavior.
32    #[must_use]
33    pub const fn new(block: BlockRef) -> Self {
34        Self { block }
35    }
36}
37
38impl BlockBehavior for TallSeagrassBlock {
39    fn update_shape(
40        &self,
41        state: BlockStateId,
42        world: &dyn ScheduledTickAccess,
43        pos: BlockPos,
44        direction: Direction,
45        _neighbor_pos: BlockPos,
46        neighbor_state: BlockStateId,
47    ) -> BlockStateId {
48        let half = state.get_value(DOUBLE_BLOCK_HALF);
49        let neighbor_is_matching_other_half = neighbor_state.get_block() == self.block
50            && neighbor_state.get_value(DOUBLE_BLOCK_HALF) != half;
51
52        if direction.get_axis() == Axis::Y
53            && (half == DoubleBlockHalf::Lower) == (direction == Direction::Up)
54            && !neighbor_is_matching_other_half
55        {
56            return vanilla_blocks::AIR.default_state();
57        }
58
59        if self.can_survive(state, world, pos) {
60            state
61        } else {
62            vanilla_blocks::AIR.default_state()
63        }
64    }
65
66    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
67        if state.get_value(DOUBLE_BLOCK_HALF) == DoubleBlockHalf::Upper {
68            let below = world.get_block_state(pos.below());
69            return below.get_block() == self.block
70                && below.get_value(DOUBLE_BLOCK_HALF) == DoubleBlockHalf::Lower;
71        }
72
73        let below_pos = pos.below();
74        let below = world.get_block_state(below_pos);
75        let current = world.get_block_state(pos);
76        let fluid = if current.get_block() == self.block {
77            water_source_fluid_state()
78        } else {
79            get_fluid_state_from_block(current)
80        };
81        world.is_face_sturdy(below, below_pos, Direction::Up)
82            && !below
83                .get_block()
84                .has_tag(&BlockTag::CANNOT_SUPPORT_SEAGRASS)
85            && fluid.is_water()
86            && fluid.is_full()
87    }
88
89    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
90        if context.place_pos().y() >= context.world.max_y_exclusive() - 1 {
91            return None;
92        }
93        if !context.is_full_water() {
94            return None;
95        }
96
97        let above_fluid =
98            get_fluid_state_from_block(context.world.get_block_state(context.place_pos().above()));
99        if !above_fluid.is_water() || !above_fluid.is_full() {
100            return None;
101        }
102
103        let state = self
104            .block
105            .default_state()
106            .set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Lower);
107        self.can_survive(state, context.world, context.place_pos())
108            .then_some(state)
109    }
110
111    fn get_clone_item_stack(
112        &self,
113        _block: BlockRef,
114        _state: BlockStateId,
115        _block_entity: Option<SharedBlockEntity>,
116        _include_data: bool,
117    ) -> Option<ItemStack> {
118        Some(ItemStack::new(&vanilla_items::SEAGRASS))
119    }
120
121    fn is_liquid_container(&self, _state: BlockStateId) -> bool {
122        true
123    }
124
125    fn place_liquid(
126        &self,
127        _level: &dyn LevelAccessor,
128        _pos: BlockPos,
129        _state: BlockStateId,
130        _fluid_state: FluidState,
131    ) -> bool {
132        false
133    }
134
135    fn can_place_liquid(&self, _state: BlockStateId, _fluid: FluidRef) -> bool {
136        false
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::behavior::init_behaviors;
143    use steel_registry::{blocks::properties::IntProperty, init_vanilla_registry, vanilla_blocks};
144
145    use crate::test_support::TestLevel;
146
147    use super::*;
148
149    const LEVEL: &IntProperty = &BlockStateProperties::LEVEL;
150
151    fn tall_seagrass_level(below: BlockStateId, current: BlockStateId) -> TestLevel {
152        TestLevel::default()
153            .with_block(BlockPos::ZERO.below(), below)
154            .with_block(BlockPos::ZERO, current)
155    }
156
157    #[test]
158    fn tall_seagrass_lower_breaks_when_upper_half_is_missing() {
159        init_vanilla_registry();
160        let behavior = TallSeagrassBlock::new(&vanilla_blocks::TALL_SEAGRASS);
161        let lower = vanilla_blocks::TALL_SEAGRASS
162            .default_state()
163            .set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Lower);
164        let level = tall_seagrass_level(vanilla_blocks::DIRT.default_state(), lower);
165
166        let updated = behavior.update_shape(
167            lower,
168            &level,
169            BlockPos::ZERO,
170            Direction::Up,
171            BlockPos::ZERO.above(),
172            vanilla_blocks::WATER.default_state(),
173        );
174
175        assert!(updated.is_air());
176    }
177
178    #[test]
179    fn tall_seagrass_upper_breaks_when_lower_half_is_missing() {
180        init_vanilla_registry();
181        let behavior = TallSeagrassBlock::new(&vanilla_blocks::TALL_SEAGRASS);
182        let upper = vanilla_blocks::TALL_SEAGRASS
183            .default_state()
184            .set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Upper);
185        let level = tall_seagrass_level(vanilla_blocks::AIR.default_state(), upper);
186
187        let updated = behavior.update_shape(
188            upper,
189            &level,
190            BlockPos::ZERO,
191            Direction::Down,
192            BlockPos::ZERO.below(),
193            vanilla_blocks::AIR.default_state(),
194        );
195
196        assert!(updated.is_air());
197    }
198
199    #[test]
200    fn tall_seagrass_lower_survives_in_falling_full_water() {
201        init_vanilla_registry();
202        init_behaviors();
203        let behavior = TallSeagrassBlock::new(&vanilla_blocks::TALL_SEAGRASS);
204        let lower = vanilla_blocks::TALL_SEAGRASS
205            .default_state()
206            .set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Lower);
207        let falling_full_water = vanilla_blocks::WATER
208            .default_state()
209            .set_value(LEVEL, LEVEL.max);
210        let level = tall_seagrass_level(vanilla_blocks::DIRT.default_state(), falling_full_water);
211
212        assert!(behavior.can_survive(lower, &level, BlockPos::ZERO));
213    }
214}