Skip to main content

steel_core/behavior/blocks/vegetation/
coral_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty, Direction};
6use steel_registry::fluid::FluidStateExt;
7use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
8
9use crate::behavior::block::BlockBehavior;
10use crate::behavior::context::BlockPlaceContext;
11use crate::world::{LevelReader, ScheduledTickAccess, World};
12
13use super::BlockRef;
14
15/// Vanilla `CoralBlock` survival.
16#[block_behavior]
17pub struct CoralBlock {
18    block: BlockRef,
19    #[json_arg(vanilla_blocks, json = "dead_block")]
20    dead_block: BlockRef,
21}
22
23const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
24const MIN_WATER_CHECK_DELAY: i32 = 60;
25
26impl CoralBlock {
27    /// Creates a new live coral block behavior.
28    #[must_use]
29    pub const fn new(block: BlockRef, dead_block: BlockRef) -> Self {
30        Self { block, dead_block }
31    }
32
33    fn dead_state(&self) -> BlockStateId {
34        self.dead_block.default_state()
35    }
36    /// Vanilla `BaseCoralPlantTypeBlock.canSurvive` (also `BaseCoralFanBlock`,
37    /// `CoralPlantBlock`, `CoralFanBlock`).
38    ///
39    /// The block below must be face-sturdy on its UP face.
40    pub(super) fn can_survive(world: &dyn LevelReader, pos: BlockPos) -> bool {
41        let below_pos = pos.below();
42        let below = world.get_block_state(below_pos);
43        world.is_face_sturdy(below, below_pos, Direction::Up)
44    }
45
46    /// Vanilla `BaseCoralPlantTypeBlock.scanForWater`.
47    pub(super) fn scan_for_water(
48        state: BlockStateId,
49        world: &dyn LevelReader,
50        pos: BlockPos,
51    ) -> bool {
52        if state.try_get_value(WATERLOGGED) == Some(true) {
53            return true;
54        }
55
56        Direction::ALL.iter().any(|direction| {
57            world
58                .get_block_state(pos.relative(*direction))
59                .get_fluid_state()
60                .is_water()
61        })
62    }
63
64    /// Vanilla `BaseCoralPlantTypeBlock.tryScheduleDieTick`.
65    pub(super) fn schedule_die_tick(
66        state: BlockStateId,
67        world: &dyn ScheduledTickAccess,
68        pos: BlockPos,
69        block: BlockRef,
70    ) {
71        if Self::scan_for_water(state, world, pos) {
72            return;
73        }
74
75        // Intentional Steel divergence: incidental runtime timing does not use world RNG.
76        let delay = MIN_WATER_CHECK_DELAY + rand::random_range(0..40);
77        let _ = world.schedule_block_tick_default(pos, block, delay);
78    }
79}
80
81impl BlockBehavior for CoralBlock {
82    fn update_shape(
83        &self,
84        state: BlockStateId,
85        world: &dyn ScheduledTickAccess,
86        pos: BlockPos,
87        _direction: Direction,
88        _neighbor_pos: BlockPos,
89        _neighbor_state: BlockStateId,
90    ) -> BlockStateId {
91        Self::schedule_die_tick(state, world, pos, self.block);
92
93        state
94    }
95
96    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
97        let state = self.block.default_state();
98        Self::schedule_die_tick(state, context.world, context.place_pos(), self.block);
99        Some(state)
100    }
101
102    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
103        if !Self::scan_for_water(state, world, pos) {
104            world.set_block(pos, self.dead_state(), UpdateFlags::UPDATE_CLIENTS);
105        }
106    }
107}