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