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::world::{LevelReader, ScheduledTickAccess, World};
18
19use super::BlockRef;
20
21/// Vanilla `KelpPlantBlock` survival and fluid state.
22#[block_behavior]
23pub struct KelpPlantBlock {
24    base: GrowingPlantBodyBlock,
25}
26
27impl KelpPlantBlock {
28    /// Creates a new kelp plant block behavior.
29    #[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}