steel_core/behavior/blocks/vegetation/
kelp_block.rs1use std::sync::Arc;
2
3use crate::behavior::block::BlockBehavior;
4use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
5use crate::behavior::blocks::vegetation::growing_plant_block;
6use crate::behavior::blocks::vegetation::growing_plant_head_block::{
7 GrowingPlantHeadBehavior, GrowingPlantHeadBlock,
8};
9use crate::behavior::context::BlockPlaceContext;
10use crate::block_entity::SharedBlockEntity;
11use crate::world::{LevelReader, ScheduledTickAccess, World};
12
13use rand::Rng;
14use steel_macros::block_behavior;
15use steel_registry::blocks::block_state_ext::BlockStateExt;
16use steel_registry::blocks::properties::Direction;
17use steel_registry::fluid::{FluidRef, FluidStateExt};
18use steel_registry::item_stack::ItemStack;
19use steel_registry::vanilla_block_tags::BlockTag;
20use steel_registry::{vanilla_blocks, vanilla_items};
21use steel_utils::{BlockPos, BlockStateId};
22
23use super::BlockRef;
24
25#[block_behavior]
27pub struct KelpBlock {
28 base: GrowingPlantHeadBlock,
29}
30
31const GROW_PER_TICK_PROBABILITY: f64 = 0.14;
32
33impl KelpBlock {
34 #[must_use]
36 pub const fn new(block: BlockRef) -> Self {
37 Self {
38 base: GrowingPlantHeadBlock::new(
39 block,
40 Direction::Up,
41 true,
42 GROW_PER_TICK_PROBABILITY,
43 &vanilla_blocks::KELP_PLANT,
44 Some(Self::get_blocks_to_grow_when_bonemealed),
45 Self::can_grow_into,
46 ),
47 }
48 }
49
50 fn can_grow_into(state: BlockStateId) -> bool {
51 state.get_block() == &vanilla_blocks::WATER
52 }
53
54 fn get_blocks_to_grow_when_bonemealed(_rng: &mut dyn Rng) -> i32 {
55 1
56 }
57 pub(crate) fn kelp_can_survive(world: &dyn LevelReader, pos: BlockPos) -> bool {
58 let attached_pos = pos.below();
59 let attached_state = world.get_block_state(attached_pos);
60 if attached_state
61 .get_block()
62 .has_tag(&BlockTag::CANNOT_SUPPORT_KELP)
63 {
64 return false;
65 }
66 growing_plant_block::can_survive(
67 world,
68 pos,
69 Direction::Up,
70 &vanilla_blocks::KELP,
71 &vanilla_blocks::KELP_PLANT,
72 )
73 }
74}
75
76impl BlockBehavior for KelpBlock {
77 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
78 Self::kelp_can_survive(world, pos)
79 }
80
81 fn get_clone_item_stack(
82 &self,
83 _block: BlockRef,
84 _state: BlockStateId,
85 _block_entity: Option<SharedBlockEntity>,
86 _include_data: bool,
87 ) -> Option<ItemStack> {
88 Some(ItemStack::new(&vanilla_items::KELP))
89 }
90
91 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
92 self.base.random_tick(state, world, pos);
93 }
94
95 fn update_shape(
96 &self,
97 state: BlockStateId,
98 world: &dyn ScheduledTickAccess,
99 pos: BlockPos,
100 direction: Direction,
101 neighbor_pos: BlockPos,
102 neighbor_state: BlockStateId,
103 ) -> BlockStateId {
104 self.base
105 .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
106 }
107
108 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
109 let fluid_state = context
110 .world
111 .get_block_state(context.place_pos())
112 .get_fluid_state();
113 if fluid_state.is_water() && fluid_state.is_full() {
114 return self.base.get_state_for_placement(context);
115 }
116 None
117 }
118
119 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
120 self.base.tick(state, world, pos);
121 }
122
123 fn is_liquid_container(&self, _state: BlockStateId) -> bool {
124 true
125 }
126
127 fn can_place_liquid(&self, _state: BlockStateId, _fluid: FluidRef) -> bool {
128 false
129 }
130
131 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
132 Some(self)
133 }
134
135 fn as_growing_plant_head(&self) -> Option<&dyn GrowingPlantHeadBehavior> {
136 Some(&self.base)
137 }
138}
139
140impl Bonemealable for KelpBlock {
141 fn is_valid_bonemeal_target(
142 &self,
143 state: BlockStateId,
144 world: &dyn LevelReader,
145 pos: BlockPos,
146 ) -> bool {
147 self.base.is_valid_bonemeal_target(state, world, pos)
148 }
149
150 fn perform_bonemeal(
151 &self,
152 state: BlockStateId,
153 world: &Arc<World>,
154 rng: &mut dyn Rng,
155 pos: BlockPos,
156 ) {
157 self.base.perform_bonemeal(state, world, rng, pos);
158 }
159
160 fn bonemeal_action_type(&self) -> BonemealAction {
161 BonemealAction::Grower
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::test_support::TestLevel;
169 use steel_registry::init_vanilla_registry;
170
171 #[test]
172 fn kelp_update_shape_schedules_water_tick() {
173 init_vanilla_registry();
174
175 let kelp = KelpBlock::new(&vanilla_blocks::KELP);
176 let level =
177 TestLevel::default().with_default_block_state(vanilla_blocks::WATER.default_state());
178 let state = vanilla_blocks::KELP.default_state();
179
180 assert_eq!(
181 kelp.update_shape(
182 state,
183 &level,
184 BlockPos::ZERO,
185 Direction::North,
186 Direction::North.relative(BlockPos::ZERO),
187 vanilla_blocks::WATER.default_state(),
188 ),
189 state
190 );
191 assert!(level.scheduled_water_tick());
192 }
193
194 #[test]
195 fn kelp_head_update_shape_schedules_break_tick_when_unsupported() {
196 init_vanilla_registry();
197
198 let kelp = KelpBlock::new(&vanilla_blocks::KELP);
199 let level =
200 TestLevel::default().with_default_block_state(vanilla_blocks::WATER.default_state());
201 let state = vanilla_blocks::KELP.default_state();
202
203 let updated = kelp.update_shape(
204 state,
205 &level,
206 BlockPos::ZERO,
207 Direction::Down,
208 BlockPos::ZERO.below(),
209 vanilla_blocks::WATER.default_state(),
210 );
211
212 assert_eq!(updated, state);
213 assert!(
214 level
215 .scheduled_block_ticks
216 .borrow()
217 .iter()
218 .any(|tick| tick.block == &vanilla_blocks::KELP && tick.delay == 1)
219 );
220 }
221
222 #[test]
223 fn kelp_head_converts_to_body_when_connected_above() {
224 init_vanilla_registry();
225
226 let kelp = KelpBlock::new(&vanilla_blocks::KELP);
227 let level =
228 TestLevel::default().with_default_block_state(vanilla_blocks::WATER.default_state());
229 let state = vanilla_blocks::KELP.default_state();
230
231 let updated = kelp.update_shape(
232 state,
233 &level,
234 BlockPos::ZERO,
235 Direction::Up,
236 BlockPos::ZERO.above(),
237 vanilla_blocks::KELP_PLANT.default_state(),
238 );
239
240 assert_eq!(updated.get_block(), &vanilla_blocks::KELP_PLANT);
241 assert!(level.scheduled_fluid_ticks.borrow().is_empty());
242 }
243}