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