Skip to main content

steel_core/behavior/blocks/vegetation/
kelp_block.rs

1use std::sync::Arc;
2
3use crate::behavior::block::BlockBehavior;
4use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
5use crate::behavior::blocks::vegetation::growing_plant_block;
6use crate::behavior::blocks::vegetation::growing_plant_head_block::{
7    GrowingPlantHeadBehavior, GrowingPlantHeadBlock,
8};
9use crate::behavior::context::BlockPlaceContext;
10use crate::block_entity::SharedBlockEntity;
11use crate::world::{LevelReader, ScheduledTickAccess, World};
12
13use rand::Rng;
14use steel_macros::block_behavior;
15use steel_registry::blocks::block_state_ext::BlockStateExt;
16use steel_registry::blocks::properties::Direction;
17use steel_registry::fluid::{FluidRef, FluidStateExt};
18use steel_registry::item_stack::ItemStack;
19use steel_registry::vanilla_block_tags::BlockTag;
20use steel_registry::{vanilla_blocks, vanilla_items};
21use steel_utils::{BlockPos, BlockStateId};
22
23use super::BlockRef;
24
25/// Vanilla `KelpBlock` survival and fluid state.
26#[block_behavior]
27pub struct KelpBlock {
28    base: GrowingPlantHeadBlock,
29}
30
31const GROW_PER_TICK_PROBABILITY: f64 = 0.14;
32
33impl KelpBlock {
34    /// Creates a new kelp block behavior.
35    #[must_use]
36    pub const fn new(block: BlockRef) -> Self {
37        Self {
38            base: GrowingPlantHeadBlock::new(
39                block,
40                Direction::Up,
41                true,
42                GROW_PER_TICK_PROBABILITY,
43                &vanilla_blocks::KELP_PLANT,
44                Some(Self::get_blocks_to_grow_when_bonemealed),
45                Self::can_grow_into,
46            ),
47        }
48    }
49
50    fn can_grow_into(state: BlockStateId) -> bool {
51        state.get_block() == &vanilla_blocks::WATER
52    }
53
54    fn get_blocks_to_grow_when_bonemealed(_rng: &mut dyn Rng) -> i32 {
55        1
56    }
57    pub(crate) fn kelp_can_survive(world: &dyn LevelReader, pos: BlockPos) -> bool {
58        let attached_pos = pos.below();
59        let attached_state = world.get_block_state(attached_pos);
60        if attached_state
61            .get_block()
62            .has_tag(&BlockTag::CANNOT_SUPPORT_KELP)
63        {
64            return false;
65        }
66        growing_plant_block::can_survive(
67            world,
68            pos,
69            Direction::Up,
70            &vanilla_blocks::KELP,
71            &vanilla_blocks::KELP_PLANT,
72        )
73    }
74}
75
76impl BlockBehavior for KelpBlock {
77    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
78        Self::kelp_can_survive(world, pos)
79    }
80
81    fn get_clone_item_stack(
82        &self,
83        _block: BlockRef,
84        _state: BlockStateId,
85        _block_entity: Option<SharedBlockEntity>,
86        _include_data: bool,
87    ) -> Option<ItemStack> {
88        Some(ItemStack::new(&vanilla_items::KELP))
89    }
90
91    fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
92        self.base.random_tick(state, world, pos);
93    }
94
95    fn update_shape(
96        &self,
97        state: BlockStateId,
98        world: &dyn ScheduledTickAccess,
99        pos: BlockPos,
100        direction: Direction,
101        neighbor_pos: BlockPos,
102        neighbor_state: BlockStateId,
103    ) -> BlockStateId {
104        self.base
105            .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
106    }
107
108    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
109        let fluid_state = context
110            .world
111            .get_block_state(context.place_pos())
112            .get_fluid_state();
113        if fluid_state.is_water() && fluid_state.is_full() {
114            return self.base.get_state_for_placement(context);
115        }
116        None
117    }
118
119    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
120        self.base.tick(state, world, pos);
121    }
122
123    fn is_liquid_container(&self, _state: BlockStateId) -> bool {
124        true
125    }
126
127    fn can_place_liquid(&self, _state: BlockStateId, _fluid: FluidRef) -> bool {
128        false
129    }
130
131    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
132        Some(self)
133    }
134
135    fn as_growing_plant_head(&self) -> Option<&dyn GrowingPlantHeadBehavior> {
136        Some(&self.base)
137    }
138}
139
140impl Bonemealable for KelpBlock {
141    fn is_valid_bonemeal_target(
142        &self,
143        state: BlockStateId,
144        world: &dyn LevelReader,
145        pos: BlockPos,
146    ) -> bool {
147        self.base.is_valid_bonemeal_target(state, world, pos)
148    }
149
150    fn perform_bonemeal(
151        &self,
152        state: BlockStateId,
153        world: &Arc<World>,
154        rng: &mut dyn Rng,
155        pos: BlockPos,
156    ) {
157        self.base.perform_bonemeal(state, world, rng, pos);
158    }
159
160    fn bonemeal_action_type(&self) -> BonemealAction {
161        BonemealAction::Grower
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::test_support::TestLevel;
169    use steel_registry::init_vanilla_registry;
170
171    #[test]
172    fn kelp_update_shape_schedules_water_tick() {
173        init_vanilla_registry();
174
175        let kelp = KelpBlock::new(&vanilla_blocks::KELP);
176        let level =
177            TestLevel::default().with_default_block_state(vanilla_blocks::WATER.default_state());
178        let state = vanilla_blocks::KELP.default_state();
179
180        assert_eq!(
181            kelp.update_shape(
182                state,
183                &level,
184                BlockPos::ZERO,
185                Direction::North,
186                Direction::North.relative(BlockPos::ZERO),
187                vanilla_blocks::WATER.default_state(),
188            ),
189            state
190        );
191        assert!(level.scheduled_water_tick());
192    }
193
194    #[test]
195    fn kelp_head_update_shape_schedules_break_tick_when_unsupported() {
196        init_vanilla_registry();
197
198        let kelp = KelpBlock::new(&vanilla_blocks::KELP);
199        let level =
200            TestLevel::default().with_default_block_state(vanilla_blocks::WATER.default_state());
201        let state = vanilla_blocks::KELP.default_state();
202
203        let updated = kelp.update_shape(
204            state,
205            &level,
206            BlockPos::ZERO,
207            Direction::Down,
208            BlockPos::ZERO.below(),
209            vanilla_blocks::WATER.default_state(),
210        );
211
212        assert_eq!(updated, state);
213        assert!(
214            level
215                .scheduled_block_ticks
216                .borrow()
217                .iter()
218                .any(|tick| tick.block == &vanilla_blocks::KELP && tick.delay == 1)
219        );
220    }
221
222    #[test]
223    fn kelp_head_converts_to_body_when_connected_above() {
224        init_vanilla_registry();
225
226        let kelp = KelpBlock::new(&vanilla_blocks::KELP);
227        let level =
228            TestLevel::default().with_default_block_state(vanilla_blocks::WATER.default_state());
229        let state = vanilla_blocks::KELP.default_state();
230
231        let updated = kelp.update_shape(
232            state,
233            &level,
234            BlockPos::ZERO,
235            Direction::Up,
236            BlockPos::ZERO.above(),
237            vanilla_blocks::KELP_PLANT.default_state(),
238        );
239
240        assert_eq!(updated.get_block(), &vanilla_blocks::KELP_PLANT);
241        assert!(level.scheduled_fluid_ticks.borrow().is_empty());
242    }
243}