Skip to main content

steel_core/behavior/blocks/vegetation/
coral_plant_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, Direction};
6use steel_registry::{vanilla_blocks, vanilla_fluids};
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, coral_plant_can_survive, coral_scan_for_water, schedule_coral_die_tick};
14
15/// Vanilla `CoralPlantBlock` survival (live coral plants such as `tube_coral`).
16///
17/// Inherits `canSurvive` from `BaseCoralPlantTypeBlock`.
18#[block_behavior]
19pub struct CoralPlantBlock {
20    block: BlockRef,
21    #[json_arg(vanilla_blocks, json = "dead_block")]
22    dead_block: BlockRef,
23}
24
25impl CoralPlantBlock {
26    /// Creates a new live coral plant block behavior.
27    #[must_use]
28    pub const fn new(block: BlockRef, dead_block: BlockRef) -> Self {
29        Self { block, dead_block }
30    }
31
32    fn dead_state(&self) -> BlockStateId {
33        self.dead_block
34            .default_state()
35            .set_value(&BlockStateProperties::WATERLOGGED, false)
36    }
37}
38
39impl BlockBehavior for CoralPlantBlock {
40    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
41        coral_plant_can_survive(world, pos)
42    }
43
44    fn on_place(
45        &self,
46        state: BlockStateId,
47        world: &Arc<World>,
48        pos: BlockPos,
49        _old_state: BlockStateId,
50        _moved_by_piston: bool,
51    ) {
52        schedule_coral_die_tick(state, world, pos, self.block);
53    }
54
55    fn update_shape(
56        &self,
57        state: BlockStateId,
58        world: &dyn ScheduledTickAccess,
59        pos: BlockPos,
60        direction: Direction,
61        _neighbor_pos: BlockPos,
62        _neighbor_state: BlockStateId,
63    ) -> BlockStateId {
64        if direction == Direction::Down && !self.can_survive(state, world, pos) {
65            return vanilla_blocks::AIR.default_state();
66        }
67
68        schedule_coral_die_tick(state, world, pos, self.block);
69
70        if state.get_value(&BlockStateProperties::WATERLOGGED) {
71            let delay = world.fluid_tick_delay(&vanilla_fluids::WATER);
72            let _ = world.schedule_fluid_tick_default(pos, &vanilla_fluids::WATER, delay);
73        }
74
75        state
76    }
77
78    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
79        let state = self.block.default_state();
80        if !self.can_survive(state, context.world, context.place_pos()) {
81            return None;
82        }
83        // Vanilla: WATERLOGGED reflects whether the click position has full water.
84        Some(state.set_value(&BlockStateProperties::WATERLOGGED, context.is_full_water()))
85    }
86
87    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
88        if !coral_scan_for_water(state, world, pos) {
89            world.set_block(pos, self.dead_state(), UpdateFlags::UPDATE_CLIENTS);
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use steel_registry::init_vanilla_registry;
97
98    use crate::behavior::init_behaviors;
99    use crate::test_support::TestLevel;
100
101    use super::*;
102
103    fn supported_level() -> TestLevel {
104        TestLevel::default().with_block(
105            BlockPos::ZERO.below(),
106            vanilla_blocks::STONE.default_state(),
107        )
108    }
109
110    #[test]
111    fn dry_coral_plant_update_shape_schedules_die_tick() {
112        init_vanilla_registry();
113        init_behaviors();
114        let behavior = CoralPlantBlock::new(
115            &vanilla_blocks::TUBE_CORAL,
116            &vanilla_blocks::DEAD_TUBE_CORAL,
117        );
118        let level = supported_level();
119        let state = vanilla_blocks::TUBE_CORAL
120            .default_state()
121            .set_value(&BlockStateProperties::WATERLOGGED, false);
122
123        assert_eq!(
124            behavior.update_shape(
125                state,
126                &level,
127                BlockPos::ZERO,
128                Direction::North,
129                BlockPos::ZERO.north(),
130                vanilla_blocks::AIR.default_state(),
131            ),
132            state
133        );
134
135        let scheduled = level.scheduled_block_ticks.borrow();
136        assert_eq!(scheduled.len(), 1);
137        assert_eq!(scheduled[0].block, &vanilla_blocks::TUBE_CORAL);
138        assert!((60..100).contains(&scheduled[0].delay));
139    }
140
141    #[test]
142    fn waterlogged_coral_plant_update_shape_schedules_water_not_die_tick() {
143        init_vanilla_registry();
144        init_behaviors();
145        let behavior = CoralPlantBlock::new(
146            &vanilla_blocks::TUBE_CORAL,
147            &vanilla_blocks::DEAD_TUBE_CORAL,
148        );
149        let level = supported_level();
150        let state = vanilla_blocks::TUBE_CORAL
151            .default_state()
152            .set_value(&BlockStateProperties::WATERLOGGED, true);
153
154        assert_eq!(
155            behavior.update_shape(
156                state,
157                &level,
158                BlockPos::ZERO,
159                Direction::North,
160                BlockPos::ZERO.north(),
161                vanilla_blocks::AIR.default_state(),
162            ),
163            state
164        );
165
166        assert!(level.scheduled_block_ticks.borrow().is_empty());
167        assert!(level.scheduled_water_tick());
168    }
169}