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