steel_core/behavior/blocks/vegetation/
glow_lichen_block.rs1use 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#[block_behavior]
18pub struct GlowLichenBlock {
19 multiface: MultifaceBlock,
20 spreader: MultifaceSpreader,
21}
22
23impl GlowLichenBlock {
24 #[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}