Skip to main content

steel_core/behavior/blocks/vegetation/
glow_lichen_block.rs

1use std::sync::Arc;
2
3use rand::Rng;
4use steel_macros::block_behavior;
5use steel_registry::blocks::properties::Direction;
6use steel_utils::{BlockPos, BlockStateId};
7
8use crate::behavior::block::BlockBehavior;
9use crate::behavior::blocks::vegetation::bonemealable::Bonemealable;
10use crate::behavior::context::BlockPlaceContext;
11use crate::world::{LevelReader, ScheduledTickAccess, World};
12
13use super::multiface_block::MultifaceSpreader;
14use super::{BlockRef, MultifaceBlock};
15
16/// Vanilla glow lichen behavior.
17#[block_behavior]
18pub struct GlowLichenBlock {
19    multiface: MultifaceBlock,
20    spreader: MultifaceSpreader,
21}
22
23impl GlowLichenBlock {
24    /// Creates a new glow lichen block behavior.
25    #[must_use]
26    pub const fn new(block: BlockRef) -> Self {
27        Self {
28            multiface: MultifaceBlock::new(block),
29            spreader: MultifaceSpreader::new(block),
30        }
31    }
32}
33
34impl BlockBehavior for GlowLichenBlock {
35    fn update_shape(
36        &self,
37        state: BlockStateId,
38        world: &dyn ScheduledTickAccess,
39        pos: BlockPos,
40        direction: Direction,
41        neighbor_pos: BlockPos,
42        neighbor_state: BlockStateId,
43    ) -> BlockStateId {
44        self.multiface
45            .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
46    }
47
48    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
49        self.multiface.can_survive(state, world, pos)
50    }
51
52    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
53        self.multiface.get_state_for_placement(context)
54    }
55
56    fn can_be_replaced(&self, state: BlockStateId, context: &BlockPlaceContext<'_>) -> bool {
57        self.multiface.can_be_replaced(state, context)
58    }
59
60    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
61        Some(self)
62    }
63}
64
65impl Bonemealable for GlowLichenBlock {
66    fn is_valid_bonemeal_target(
67        &self,
68        state: BlockStateId,
69        world: &dyn LevelReader,
70        pos: BlockPos,
71    ) -> bool {
72        Direction::ALL.iter().any(|face| {
73            self.spreader
74                .can_spread_in_any_direction(state, world, pos, face.opposite())
75        })
76    }
77
78    fn perform_bonemeal(
79        &self,
80        state: BlockStateId,
81        world: &Arc<World>,
82        rng: &mut dyn Rng,
83        pos: BlockPos,
84    ) {
85        self.spreader
86            .spread_from_random_face_toward_random_direction(state, world, pos, rng);
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use steel_registry::blocks::block_state_ext::BlockStateExt;
93    use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
94    use steel_registry::{init_vanilla_registry, vanilla_blocks};
95    use steel_utils::types::UpdateFlags;
96
97    use crate::test_support::TestLevel;
98
99    use super::*;
100
101    const DOWN: &BoolProperty = &BlockStateProperties::DOWN;
102    const EAST: &BoolProperty = &BlockStateProperties::EAST;
103    const NORTH: &BoolProperty = &BlockStateProperties::NORTH;
104    const SOUTH: &BoolProperty = &BlockStateProperties::SOUTH;
105    const UP: &BoolProperty = &BlockStateProperties::UP;
106    const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
107    const WEST: &BoolProperty = &BlockStateProperties::WEST;
108
109    fn north_facing_state() -> BlockStateId {
110        vanilla_blocks::GLOW_LICHEN
111            .default_state()
112            .set_value(NORTH, true)
113    }
114
115    #[test]
116    fn spreads_to_an_open_face_at_the_same_position_first() {
117        init_vanilla_registry();
118        let pos = BlockPos::new(0, 64, 0);
119        let state = north_facing_state();
120        let level = TestLevel::default()
121            .with_block(pos, state)
122            .with_block(pos.north(), vanilla_blocks::STONE.default_state())
123            .with_block(pos.above(), vanilla_blocks::STONE.default_state());
124        let spreader = MultifaceSpreader::new(&vanilla_blocks::GLOW_LICHEN);
125        let behavior = GlowLichenBlock::new(&vanilla_blocks::GLOW_LICHEN);
126
127        assert!(behavior.is_valid_bonemeal_target(state, &level, pos));
128
129        let spread = spreader
130            .spread_from_face_toward_direction(state, &level, pos, Direction::North, Direction::Up)
131            .expect("lichen should spread at the source position");
132
133        assert_eq!(spread.pos, pos);
134        assert_eq!(spread.face, Direction::Up);
135        assert!(level.get_block_state(pos).get_value(UP));
136        assert!(level.get_block_state(pos).get_value(NORTH));
137        assert_eq!(
138            level.placed_blocks.borrow()[0].flags,
139            UpdateFlags::UPDATE_CLIENTS
140        );
141    }
142
143    #[test]
144    fn spreads_in_the_same_plane_when_the_source_has_no_open_face() {
145        init_vanilla_registry();
146        let pos = BlockPos::new(0, 64, 0);
147        let state = north_facing_state();
148        let target = pos.above();
149        let level = TestLevel::default()
150            .with_block(pos, state)
151            .with_block(pos.north(), vanilla_blocks::STONE.default_state())
152            .with_block(target.north(), vanilla_blocks::STONE.default_state());
153        let spreader = MultifaceSpreader::new(&vanilla_blocks::GLOW_LICHEN);
154
155        let spread = spreader
156            .spread_from_face_toward_direction(state, &level, pos, Direction::North, Direction::Up)
157            .expect("lichen should spread in the same plane");
158
159        assert_eq!(spread.pos, target);
160        assert_eq!(spread.face, Direction::North);
161        assert!(level.get_block_state(target).get_value(NORTH));
162    }
163
164    #[test]
165    fn wraps_around_a_supporting_block_when_same_plane_is_blocked() {
166        init_vanilla_registry();
167        let pos = BlockPos::new(0, 64, 0);
168        let state = north_facing_state();
169        let target = pos.above().north();
170        let level = TestLevel::default()
171            .with_block(pos, state)
172            .with_block(pos.north(), vanilla_blocks::STONE.default_state());
173        let spreader = MultifaceSpreader::new(&vanilla_blocks::GLOW_LICHEN);
174
175        let spread = spreader
176            .spread_from_face_toward_direction(state, &level, pos, Direction::North, Direction::Up)
177            .expect("lichen should wrap around its support");
178
179        assert_eq!(spread.pos, target);
180        assert_eq!(spread.face, Direction::Down);
181        assert!(level.get_block_state(target).get_value(DOWN));
182    }
183
184    #[test]
185    fn spreading_into_source_water_creates_waterlogged_lichen() {
186        init_vanilla_registry();
187        let pos = BlockPos::new(0, 64, 0);
188        let state = north_facing_state();
189        let target = pos.above();
190        let level = TestLevel::default()
191            .with_block(pos, state)
192            .with_block(pos.north(), vanilla_blocks::STONE.default_state())
193            .with_block(target, vanilla_blocks::WATER.default_state())
194            .with_block(target.north(), vanilla_blocks::STONE.default_state());
195        let spreader = MultifaceSpreader::new(&vanilla_blocks::GLOW_LICHEN);
196
197        let spread = spreader
198            .spread_from_face_toward_direction(state, &level, pos, Direction::North, Direction::Up)
199            .expect("lichen should spread into source water");
200
201        assert_eq!(spread.pos, target);
202        assert!(level.get_block_state(target).get_value(WATERLOGGED));
203    }
204
205    #[test]
206    fn bonemeal_requires_a_vacant_spread_direction() {
207        init_vanilla_registry();
208        let pos = BlockPos::new(0, 64, 0);
209        let full_state = vanilla_blocks::GLOW_LICHEN
210            .default_state()
211            .set_value(DOWN, true)
212            .set_value(UP, true)
213            .set_value(NORTH, true)
214            .set_value(SOUTH, true)
215            .set_value(WEST, true)
216            .set_value(EAST, true);
217        let level = TestLevel::default().with_block(pos, full_state);
218        let behavior = GlowLichenBlock::new(&vanilla_blocks::GLOW_LICHEN);
219
220        assert!(!behavior.is_valid_bonemeal_target(full_state, &level, pos));
221    }
222}