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