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