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