steel_core/behavior/blocks/vegetation/
snowy_block.rs1use steel_macros::block_behavior;
2use steel_registry::blocks::BlockRef;
3use steel_registry::blocks::block_state_ext::BlockStateExt;
4use steel_registry::blocks::properties::BlockStateProperties;
5use steel_registry::vanilla_block_tags::BlockTag;
6use steel_utils::{BlockPos, BlockStateId, Direction};
7
8use crate::behavior::block::BlockBehavior;
9use crate::behavior::context::BlockPlaceContext;
10use crate::world::ScheduledTickAccess;
11
12#[must_use]
13pub(super) fn is_snowy_setting(above_state: BlockStateId) -> bool {
14 above_state.get_block().has_tag(&BlockTag::SNOW)
15}
16
17#[must_use]
18pub(super) fn snowy_placement_state(
19 block: BlockRef,
20 context: &BlockPlaceContext<'_>,
21) -> BlockStateId {
22 let above = context.world.get_block_state(context.place_pos().above());
23 block
24 .default_state()
25 .set_value(&BlockStateProperties::SNOWY, is_snowy_setting(above))
26}
27
28#[must_use]
29pub(super) fn update_snowy_shape(
30 state: BlockStateId,
31 direction: Direction,
32 neighbor_state: BlockStateId,
33) -> BlockStateId {
34 if direction == Direction::Up {
35 state.set_value(
36 &BlockStateProperties::SNOWY,
37 is_snowy_setting(neighbor_state),
38 )
39 } else {
40 state
41 }
42}
43
44#[block_behavior]
46pub struct SnowyBlock {
47 block: BlockRef,
48}
49
50impl SnowyBlock {
51 #[must_use]
53 pub const fn new(block: BlockRef) -> Self {
54 Self { block }
55 }
56}
57
58impl BlockBehavior for SnowyBlock {
59 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
60 Some(snowy_placement_state(self.block, context))
61 }
62
63 fn update_shape(
64 &self,
65 state: BlockStateId,
66 _world: &dyn ScheduledTickAccess,
67 _pos: BlockPos,
68 direction: Direction,
69 _neighbor_pos: BlockPos,
70 neighbor_state: BlockStateId,
71 ) -> BlockStateId {
72 update_snowy_shape(state, direction, neighbor_state)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
79 use steel_registry::item_stack::ItemStack;
80 use steel_registry::{init_vanilla_registry, vanilla_blocks};
81 use steel_utils::types::UpdateFlags;
82 use steel_utils::{BlockPos, ChunkPos, Direction};
83
84 use super::*;
85 use crate::behavior::init_behaviors;
86 use crate::test_support::{TestLevel, fresh_test_world, insert_ready_full_chunk};
87
88 const SNOWY: &BoolProperty = &BlockStateProperties::SNOWY;
89
90 #[test]
91 fn placement_resolves_snowy_from_above_block() {
92 init_vanilla_registry();
93 init_behaviors();
94
95 let world = fresh_test_world("snowy_block_placement");
96 let pos = BlockPos::new(0, 64, 0);
97 insert_ready_full_chunk(&world, ChunkPos::from_block_pos(pos));
98
99 let behavior = SnowyBlock::new(&vanilla_blocks::PODZOL);
100
101 for snow in [
102 &vanilla_blocks::SNOW,
103 &vanilla_blocks::SNOW_BLOCK,
104 &vanilla_blocks::POWDER_SNOW,
105 ] {
106 world.set_block(pos.above(), snow.default_state(), UpdateFlags::empty());
107 let mut stack = ItemStack::empty();
108 let context = BlockPlaceContext::directional(
109 &world,
110 pos,
111 Direction::Down,
112 &mut stack,
113 Direction::Up,
114 );
115 let state = behavior
116 .get_state_for_placement(&context)
117 .expect("placement state should exist");
118 assert!(state.get_value(SNOWY));
119 }
120
121 for non_snow in [
122 &vanilla_blocks::AIR,
123 &vanilla_blocks::STONE,
124 &vanilla_blocks::ICE,
125 ] {
126 world.set_block(pos.above(), non_snow.default_state(), UpdateFlags::empty());
127 let mut stack = ItemStack::empty();
128 let context = BlockPlaceContext::directional(
129 &world,
130 pos,
131 Direction::Down,
132 &mut stack,
133 Direction::Up,
134 );
135 let state = behavior
136 .get_state_for_placement(&context)
137 .expect("placement state should exist");
138 assert!(!state.get_value(SNOWY));
139 }
140 }
141
142 #[test]
143 fn upward_neighbor_update_recalculates_snowy() {
144 init_vanilla_registry();
145 init_behaviors();
146
147 let level = TestLevel::default();
148 let pos = BlockPos::new(0, 64, 0);
149 let behavior = SnowyBlock::new(&vanilla_blocks::PODZOL);
150
151 let base = vanilla_blocks::PODZOL
152 .default_state()
153 .set_value(SNOWY, false);
154 let snowy = vanilla_blocks::PODZOL
155 .default_state()
156 .set_value(SNOWY, true);
157
158 for snow in [
159 &vanilla_blocks::SNOW,
160 &vanilla_blocks::SNOW_BLOCK,
161 &vanilla_blocks::POWDER_SNOW,
162 ] {
163 let state = behavior.update_shape(
164 base,
165 &level,
166 pos,
167 Direction::Up,
168 pos.above(),
169 snow.default_state(),
170 );
171 assert!(state.get_value(SNOWY));
172 }
173
174 for non_snow in [
175 &vanilla_blocks::AIR,
176 &vanilla_blocks::STONE,
177 &vanilla_blocks::ICE,
178 ] {
179 let state = behavior.update_shape(
180 snowy,
181 &level,
182 pos,
183 Direction::Up,
184 pos.above(),
185 non_snow.default_state(),
186 );
187 assert!(!state.get_value(SNOWY));
188 }
189 }
190
191 #[test]
192 fn lateral_and_downward_neighbor_updates_keep_existing_state() {
193 init_vanilla_registry();
194 init_behaviors();
195
196 let level = TestLevel::default();
197 let pos = BlockPos::new(0, 64, 0);
198 let behavior = SnowyBlock::new(&vanilla_blocks::PODZOL);
199
200 let base = vanilla_blocks::PODZOL
201 .default_state()
202 .set_value(SNOWY, false);
203 let snowy = vanilla_blocks::PODZOL
204 .default_state()
205 .set_value(SNOWY, true);
206
207 for dir in [
208 Direction::Down,
209 Direction::North,
210 Direction::South,
211 Direction::East,
212 Direction::West,
213 ] {
214 let neighbor_pos = dir.relative(pos);
215
216 let updated_snowy = behavior.update_shape(
217 snowy,
218 &level,
219 pos,
220 dir,
221 neighbor_pos,
222 vanilla_blocks::AIR.default_state(),
223 );
224 assert_eq!(updated_snowy, snowy);
225
226 let updated_base = behavior.update_shape(
227 base,
228 &level,
229 pos,
230 dir,
231 neighbor_pos,
232 vanilla_blocks::SNOW.default_state(),
233 );
234 assert_eq!(updated_base, base);
235 }
236 }
237}