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