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