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