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