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