steel_core/behavior/blocks/vegetation/
coral_plant_block.rs1use 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#[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 #[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 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}