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