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::{
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#[block_behavior]
22pub struct SeagrassBlock {
23 block: BlockRef,
24}
25
26const DOUBLE_BLOCK_HALF: &EnumProperty<DoubleBlockHalf> = &BlockStateProperties::DOUBLE_BLOCK_HALF;
27
28impl SeagrassBlock {
29 #[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}