Skip to main content

steel_core/behavior/blocks/vegetation/
seagrass_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::blocks::properties::{
6    BlockStateProperties, Direction, DoubleBlockHalf, EnumProperty,
7};
8use steel_registry::fluid::{FluidRef, FluidState};
9use steel_registry::vanilla_block_tags::BlockTag;
10use steel_registry::{vanilla_blocks, vanilla_fluids};
11use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
12
13use crate::behavior::block::BlockBehavior;
14use crate::behavior::blocks::vegetation::bonemealable::Bonemealable;
15use crate::behavior::context::BlockPlaceContext;
16use crate::world::{LevelAccessor, LevelReader, ScheduledTickAccess, World};
17
18use super::BlockRef;
19
20/// Behavior for seagrass blocks.
21#[block_behavior]
22pub struct SeagrassBlock {
23    block: BlockRef,
24}
25
26const DOUBLE_BLOCK_HALF: &EnumProperty<DoubleBlockHalf> = &BlockStateProperties::DOUBLE_BLOCK_HALF;
27
28impl SeagrassBlock {
29    /// Creates a new seagrass block behavior.
30    #[must_use]
31    pub const fn new(block: BlockRef) -> Self {
32        Self { block }
33    }
34}
35
36impl BlockBehavior for SeagrassBlock {
37    fn update_shape(
38        &self,
39        state: BlockStateId,
40        world: &dyn ScheduledTickAccess,
41        pos: BlockPos,
42        _direction: Direction,
43        _neighbor_pos: BlockPos,
44        _neighbor_state: BlockStateId,
45    ) -> BlockStateId {
46        let updated = if self.can_survive(state, world, pos) {
47            state
48        } else {
49            vanilla_blocks::AIR.default_state()
50        };
51
52        if !updated.is_air() {
53            let delay = world.fluid_tick_delay(&vanilla_fluids::WATER);
54            let _ = world.schedule_fluid_tick_default(pos, &vanilla_fluids::WATER, delay);
55        }
56
57        updated
58    }
59
60    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
61        let below_pos = pos.below();
62        let below = world.get_block_state(below_pos);
63        world.is_face_sturdy(below, below_pos, Direction::Up)
64            && !below
65                .get_block()
66                .has_tag(&BlockTag::CANNOT_SUPPORT_SEAGRASS)
67    }
68
69    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
70        let state = self.block.default_state();
71        (context.is_full_water() && self.can_survive(state, context.world, context.place_pos()))
72            .then_some(state)
73    }
74
75    fn is_liquid_container(&self, _state: BlockStateId) -> bool {
76        true
77    }
78
79    fn place_liquid(
80        &self,
81        _level: &dyn LevelAccessor,
82        _pos: BlockPos,
83        _state: BlockStateId,
84        _fluid_state: FluidState,
85    ) -> bool {
86        false
87    }
88
89    fn can_place_liquid(&self, _state: BlockStateId, _fluid: FluidRef) -> bool {
90        false
91    }
92
93    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
94        Some(self)
95    }
96}
97
98impl Bonemealable for SeagrassBlock {
99    fn is_valid_bonemeal_target(
100        &self,
101        _state: BlockStateId,
102        world: &dyn LevelReader,
103        pos: BlockPos,
104    ) -> bool {
105        world.get_block_state(pos.above()).get_block() == &vanilla_blocks::WATER
106    }
107
108    fn perform_bonemeal(
109        &self,
110        _state: BlockStateId,
111        world: &Arc<World>,
112        _rng: &mut dyn rand::Rng,
113        pos: BlockPos,
114    ) {
115        let lower_state = vanilla_blocks::TALL_SEAGRASS
116            .default_state()
117            .set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Lower);
118        let upper_state = lower_state.set_value(DOUBLE_BLOCK_HALF, DoubleBlockHalf::Upper);
119        world.set_block(pos, lower_state, UpdateFlags::UPDATE_CLIENTS);
120        world.set_block(pos.above(), upper_state, UpdateFlags::UPDATE_CLIENTS);
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use steel_registry::{blocks::properties::BoolProperty, init_vanilla_registry, vanilla_blocks};
127
128    use crate::test_support::TestLevel;
129
130    use super::*;
131
132    const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
133
134    fn seagrass_level(support: BlockStateId, above: BlockStateId) -> TestLevel {
135        TestLevel::default()
136            .with_block(BlockPos::ZERO.below(), support)
137            .with_block(BlockPos::ZERO.above(), above)
138    }
139
140    #[test]
141    fn seagrass_update_shape_breaks_without_support() {
142        init_vanilla_registry();
143        let behavior = SeagrassBlock::new(&vanilla_blocks::SEAGRASS);
144        let level = seagrass_level(
145            vanilla_blocks::AIR.default_state(),
146            vanilla_blocks::AIR.default_state(),
147        );
148        let state = vanilla_blocks::SEAGRASS.default_state();
149
150        let updated = behavior.update_shape(
151            state,
152            &level,
153            BlockPos::ZERO,
154            Direction::Down,
155            BlockPos::ZERO.below(),
156            vanilla_blocks::AIR.default_state(),
157        );
158
159        assert!(updated.is_air());
160        assert!(!level.scheduled_water_tick());
161    }
162
163    #[test]
164    fn seagrass_update_shape_schedules_water_when_it_survives() {
165        init_vanilla_registry();
166        let behavior = SeagrassBlock::new(&vanilla_blocks::SEAGRASS);
167        let level = seagrass_level(
168            vanilla_blocks::DIRT.default_state(),
169            vanilla_blocks::AIR.default_state(),
170        );
171        let state = vanilla_blocks::SEAGRASS.default_state();
172
173        let updated = behavior.update_shape(
174            state,
175            &level,
176            BlockPos::ZERO,
177            Direction::Down,
178            BlockPos::ZERO.below(),
179            vanilla_blocks::DIRT.default_state(),
180        );
181
182        assert_eq!(updated, state);
183        assert!(level.scheduled_water_tick());
184    }
185
186    #[test]
187    fn seagrass_bonemeal_requires_water_block_above() {
188        init_vanilla_registry();
189        let behavior = SeagrassBlock::new(&vanilla_blocks::SEAGRASS);
190        let state = vanilla_blocks::SEAGRASS.default_state();
191        let waterlogged_slab = vanilla_blocks::OAK_SLAB
192            .default_state()
193            .set_value(WATERLOGGED, true);
194
195        let water_level = seagrass_level(
196            vanilla_blocks::DIRT.default_state(),
197            vanilla_blocks::WATER.default_state(),
198        );
199        assert!(behavior.is_valid_bonemeal_target(state, &water_level, BlockPos::ZERO));
200
201        let waterlogged_level =
202            seagrass_level(vanilla_blocks::DIRT.default_state(), waterlogged_slab);
203        assert!(!behavior.is_valid_bonemeal_target(state, &waterlogged_level, BlockPos::ZERO));
204    }
205}