steel_core/behavior/blocks/vegetation/
seagrass_block.rs1use 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#[block_behavior]
20pub struct SeagrassBlock {
21 block: BlockRef,
22}
23
24impl SeagrassBlock {
25 #[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}