steel_core/behavior/blocks/building/
fence_block.rs1use std::sync::Arc;
6
7use crate::behavior::block::BlockBehavior;
8use crate::behavior::context::BlockPlaceContext;
9use crate::world::{LevelReader, ScheduledTickAccess, World};
10use steel_macros::block_behavior;
11use steel_registry::blocks::BlockRef;
12use steel_registry::blocks::block_state_ext::BlockStateExt;
13use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty, Direction};
14use steel_registry::vanilla_block_tags::BlockTag;
15use steel_registry::vanilla_fluids;
16use steel_utils::{BlockPos, BlockStateId};
17
18#[block_behavior]
26pub struct FenceBlock {
27 block: BlockRef,
28}
29
30impl FenceBlock {
31 pub const NORTH: BoolProperty = BlockStateProperties::NORTH;
33 pub const EAST: BoolProperty = BlockStateProperties::EAST;
35 pub const SOUTH: BoolProperty = BlockStateProperties::SOUTH;
37 pub const WEST: BoolProperty = BlockStateProperties::WEST;
39 pub const WATERLOGGED: BoolProperty = BlockStateProperties::WATERLOGGED;
41
42 #[must_use]
44 pub const fn new(block: BlockRef) -> Self {
45 Self { block }
46 }
47
48 fn connects_to(
50 world: &dyn LevelReader,
51 neighbor_state: BlockStateId,
52 neighbor_pos: BlockPos,
53 direction: Direction,
54 ) -> bool {
55 let neighbor_block = neighbor_state.get_block();
56
57 if neighbor_block.has_tag(&BlockTag::FENCES) {
59 return true;
60 }
61
62 if neighbor_block.has_tag(&BlockTag::FENCE_GATES) {
64 if let Some(gate_facing) =
68 neighbor_state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING)
69 {
70 let connects = match (gate_facing, direction) {
72 (Direction::North | Direction::South, Direction::East | Direction::West)
75 | (Direction::East | Direction::West, Direction::North | Direction::South) => {
76 true
77 }
78 _ => false,
79 };
80 if connects {
81 return true;
82 }
83 }
84 }
85
86 let opposite = match direction {
88 Direction::North => Direction::South,
89 Direction::South => Direction::North,
90 Direction::East => Direction::West,
91 Direction::West => Direction::East,
92 Direction::Up => Direction::Down,
93 Direction::Down => Direction::Up,
94 };
95 world.is_face_sturdy(neighbor_state, neighbor_pos, opposite)
96 }
97
98 fn get_connection_state(&self, world: &Arc<World>, pos: BlockPos) -> BlockStateId {
100 let mut state = self.block.default_state();
101
102 let north_pos = Direction::North.relative(pos);
104 let north_state = world.get_block_state(north_pos);
105 let connects_north = Self::connects_to(world, north_state, north_pos, Direction::North);
106 state = state.set_value(&Self::NORTH, connects_north);
107
108 let east_pos = Direction::East.relative(pos);
110 let east_state = world.get_block_state(east_pos);
111 let connects_east = Self::connects_to(world, east_state, east_pos, Direction::East);
112 state = state.set_value(&Self::EAST, connects_east);
113
114 let south_pos = Direction::South.relative(pos);
116 let south_state = world.get_block_state(south_pos);
117 let connects_south = Self::connects_to(world, south_state, south_pos, Direction::South);
118 state = state.set_value(&Self::SOUTH, connects_south);
119
120 let west_pos = Direction::West.relative(pos);
122 let west_state = world.get_block_state(west_pos);
123 let connects_west = Self::connects_to(world, west_state, west_pos, Direction::West);
124 state = state.set_value(&Self::WEST, connects_west);
125
126 state
127 }
128}
129
130impl BlockBehavior for FenceBlock {
131 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
132 log::debug!(
133 "FenceBlock::get_state_for_placement called for {:?} at {:?}",
134 self.block.key,
135 context.place_pos()
136 );
137 Some(
138 self.get_connection_state(context.world, context.place_pos())
139 .set_value(&Self::WATERLOGGED, context.is_water_source()),
140 )
141 }
142
143 fn update_shape(
144 &self,
145 state: BlockStateId,
146 world: &dyn ScheduledTickAccess,
147 pos: BlockPos,
148 direction: Direction,
149 neighbor_pos: BlockPos,
150 neighbor_state: BlockStateId,
151 ) -> BlockStateId {
152 if state.get_value(&Self::WATERLOGGED) {
153 let delay = world.fluid_tick_delay(&vanilla_fluids::WATER);
154 let _ = world.schedule_fluid_tick_default(pos, &vanilla_fluids::WATER, delay);
155 }
156
157 match direction {
159 Direction::North => {
160 let connects =
161 Self::connects_to(world, neighbor_state, neighbor_pos, Direction::North);
162 state.set_value(&Self::NORTH, connects)
163 }
164 Direction::East => {
165 let connects =
166 Self::connects_to(world, neighbor_state, neighbor_pos, Direction::East);
167 state.set_value(&Self::EAST, connects)
168 }
169 Direction::South => {
170 let connects =
171 Self::connects_to(world, neighbor_state, neighbor_pos, Direction::South);
172 state.set_value(&Self::SOUTH, connects)
173 }
174 Direction::West => {
175 let connects =
176 Self::connects_to(world, neighbor_state, neighbor_pos, Direction::West);
177 state.set_value(&Self::WEST, connects)
178 }
179 Direction::Up | Direction::Down => state,
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use steel_registry::{init_vanilla_registry, vanilla_blocks};
188 use steel_utils::BlockPos;
189
190 use crate::test_support::TestLevel;
191
192 use super::*;
193
194 #[test]
195 fn waterlogged_fence_update_shape_schedules_water_tick() {
196 init_vanilla_registry();
197
198 let behavior = FenceBlock::new(&vanilla_blocks::OAK_FENCE);
199 let state = vanilla_blocks::OAK_FENCE
200 .default_state()
201 .set_value(&FenceBlock::WATERLOGGED, true);
202 let level = TestLevel::default();
203
204 let updated = behavior.update_shape(
205 state,
206 &level,
207 BlockPos::ZERO,
208 Direction::Up,
209 Direction::Up.relative(BlockPos::ZERO),
210 vanilla_blocks::AIR.default_state(),
211 );
212
213 assert_eq!(updated, state);
214 assert_eq!(
215 level
216 .scheduled_fluid_ticks
217 .borrow()
218 .iter()
219 .map(|tick| (tick.fluid, tick.delay))
220 .collect::<Vec<_>>(),
221 vec![(&vanilla_fluids::WATER, 5)]
222 );
223 }
224}