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