Skip to main content

steel_core/behavior/blocks/vegetation/
chorus_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, BoolProperty};
6use steel_registry::vanilla_block_tags::BlockTag;
7use steel_registry::vanilla_blocks;
8use steel_utils::{BlockPos, BlockStateId, Direction};
9
10use crate::behavior::block::BlockBehavior;
11use crate::behavior::context::BlockPlaceContext;
12use crate::entity::ai::path::PathComputationType;
13use crate::world::{LevelReader, ScheduledTickAccess, World};
14
15use super::BlockRef;
16
17const HORIZONTAL_DIRECTIONS: [Direction; 4] = [
18    Direction::North,
19    Direction::East,
20    Direction::South,
21    Direction::West,
22];
23
24/// Vanilla `ChorusPlantBlock` connection and survival behavior.
25#[block_behavior]
26pub struct ChorusPlantBlock {
27    block: BlockRef,
28}
29
30const DOWN: &BoolProperty = &BlockStateProperties::DOWN;
31const EAST: &BoolProperty = &BlockStateProperties::EAST;
32const NORTH: &BoolProperty = &BlockStateProperties::NORTH;
33const SOUTH: &BoolProperty = &BlockStateProperties::SOUTH;
34const UP: &BoolProperty = &BlockStateProperties::UP;
35const WEST: &BoolProperty = &BlockStateProperties::WEST;
36
37impl ChorusPlantBlock {
38    /// Creates a new chorus plant block behavior.
39    #[must_use]
40    pub const fn new(block: BlockRef) -> Self {
41        Self { block }
42    }
43
44    #[must_use]
45    pub(crate) fn state_with_connections(
46        world: &dyn LevelReader,
47        pos: BlockPos,
48        mut state: BlockStateId,
49    ) -> BlockStateId {
50        let down = world.get_block_state(pos.below());
51        let up = world.get_block_state(pos.above());
52        let north = world.get_block_state(pos.north());
53        let east = world.get_block_state(pos.east());
54        let south = world.get_block_state(pos.south());
55        let west = world.get_block_state(pos.west());
56        let block = state.get_block();
57
58        state = state.set_value(
59            DOWN,
60            down.get_block() == block
61                || down.get_block() == &vanilla_blocks::CHORUS_FLOWER
62                || down.get_block().has_tag(&BlockTag::SUPPORTS_CHORUS_PLANT),
63        );
64        state = state.set_value(
65            UP,
66            up.get_block() == block || up.get_block() == &vanilla_blocks::CHORUS_FLOWER,
67        );
68        state = state.set_value(
69            NORTH,
70            north.get_block() == block || north.get_block() == &vanilla_blocks::CHORUS_FLOWER,
71        );
72        state = state.set_value(
73            EAST,
74            east.get_block() == block || east.get_block() == &vanilla_blocks::CHORUS_FLOWER,
75        );
76        state = state.set_value(
77            SOUTH,
78            south.get_block() == block || south.get_block() == &vanilla_blocks::CHORUS_FLOWER,
79        );
80        state.set_value(
81            WEST,
82            west.get_block() == block || west.get_block() == &vanilla_blocks::CHORUS_FLOWER,
83        )
84    }
85
86    const fn property_for_direction(direction: Direction) -> &'static BoolProperty {
87        match direction {
88            Direction::Down => DOWN,
89            Direction::Up => UP,
90            Direction::North => NORTH,
91            Direction::South => SOUTH,
92            Direction::West => WEST,
93            Direction::East => EAST,
94        }
95    }
96}
97
98impl BlockBehavior for ChorusPlantBlock {
99    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
100        let below_state = world.get_block_state(pos.below());
101        let block_above_or_below =
102            !world.get_block_state(pos.above()).is_air() && !below_state.is_air();
103
104        for direction in HORIZONTAL_DIRECTIONS {
105            let neighbor_pos = pos.relative(direction);
106            let neighbor_state = world.get_block_state(neighbor_pos);
107            if neighbor_state.get_block() == self.block {
108                if block_above_or_below {
109                    return false;
110                }
111
112                let below = world.get_block_state(neighbor_pos.below());
113                if below.get_block() == self.block
114                    || below.get_block().has_tag(&BlockTag::SUPPORTS_CHORUS_PLANT)
115                {
116                    return true;
117                }
118            }
119        }
120
121        below_state.get_block() == self.block
122            || below_state
123                .get_block()
124                .has_tag(&BlockTag::SUPPORTS_CHORUS_PLANT)
125    }
126
127    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
128        Some(Self::state_with_connections(
129            context.world.as_ref(),
130            context.place_pos(),
131            self.block.default_state(),
132        ))
133    }
134
135    fn update_shape(
136        &self,
137        state: BlockStateId,
138        world: &dyn ScheduledTickAccess,
139        pos: BlockPos,
140        direction: Direction,
141        _neighbor_pos: BlockPos,
142        neighbor_state: BlockStateId,
143    ) -> BlockStateId {
144        if !self.can_survive(state, world, pos) {
145            world.schedule_block_tick_default(pos, self.block, 1);
146            return state;
147        }
148
149        let connects = neighbor_state.get_block() == self.block
150            || neighbor_state.get_block() == &vanilla_blocks::CHORUS_FLOWER
151            || direction == Direction::Down
152                && neighbor_state
153                    .get_block()
154                    .has_tag(&BlockTag::SUPPORTS_CHORUS_PLANT);
155        state.set_value(Self::property_for_direction(direction), connects)
156    }
157
158    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
159        if !self.can_survive(state, world, pos) {
160            world.destroy_block(pos, true);
161        }
162    }
163
164    fn is_pathfindable(
165        &self,
166        _state: BlockStateId,
167        _computation_type: PathComputationType,
168    ) -> bool {
169        false
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use steel_registry::init_vanilla_registry;
176
177    use crate::test_support::TestLevel;
178
179    use super::*;
180
181    #[test]
182    fn update_shape_tracks_vertical_connections() {
183        init_vanilla_registry();
184        let behavior = ChorusPlantBlock::new(&vanilla_blocks::CHORUS_PLANT);
185        let pos = BlockPos::ZERO;
186        let state = vanilla_blocks::CHORUS_PLANT.default_state();
187        let level =
188            TestLevel::default().with_block(pos.below(), vanilla_blocks::END_STONE.default_state());
189
190        let connected = behavior.update_shape(
191            state,
192            &level,
193            pos,
194            Direction::Up,
195            pos.above(),
196            vanilla_blocks::CHORUS_FLOWER.default_state(),
197        );
198        assert!(connected.get_value(UP));
199
200        let disconnected = behavior.update_shape(
201            connected,
202            &level,
203            pos,
204            Direction::Up,
205            pos.above(),
206            vanilla_blocks::AIR.default_state(),
207        );
208        assert!(!disconnected.get_value(UP));
209    }
210
211    #[test]
212    fn update_shape_schedules_tick_when_unsupported() {
213        init_vanilla_registry();
214        let behavior = ChorusPlantBlock::new(&vanilla_blocks::CHORUS_PLANT);
215        let level = TestLevel::default();
216        let pos = BlockPos::ZERO;
217        let state = vanilla_blocks::CHORUS_PLANT.default_state();
218
219        assert_eq!(
220            behavior.update_shape(
221                state,
222                &level,
223                pos,
224                Direction::Down,
225                pos.below(),
226                vanilla_blocks::AIR.default_state(),
227            ),
228            state
229        );
230
231        let scheduled = level.scheduled_block_ticks.borrow();
232        assert_eq!(scheduled.len(), 1);
233        assert_eq!(scheduled[0].pos, pos);
234        assert_eq!(scheduled[0].block, &vanilla_blocks::CHORUS_PLANT);
235        assert_eq!(scheduled[0].delay, 1);
236    }
237
238    #[test]
239    fn is_never_pathfindable() {
240        init_vanilla_registry();
241        let behavior = ChorusPlantBlock::new(&vanilla_blocks::CHORUS_PLANT);
242        let state = vanilla_blocks::CHORUS_PLANT.default_state();
243
244        assert!(!behavior.is_pathfindable(state, PathComputationType::Land));
245        assert!(!behavior.is_pathfindable(state, PathComputationType::Water));
246        assert!(!behavior.is_pathfindable(state, PathComputationType::Air));
247    }
248}