Skip to main content

steel_core/behavior/blocks/vegetation/
kelp_plant_block.rs

1use 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/// Vanilla `KelpPlantBlock` survival and fluid state.
23#[block_behavior]
24pub struct KelpPlantBlock {
25    base: GrowingPlantBodyBlock,
26}
27
28impl KelpPlantBlock {
29    /// Creates a new kelp plant block behavior.
30    #[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}