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