Skip to main content

steel_core/behavior/blocks/building/
fence_block.rs

1//! Fence block behavior implementation.
2//!
3//! Fences connect to adjacent fences, fence gates, and solid blocks.
4
5use 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/// Behavior for fence blocks.
19///
20/// Fences have 4 boolean properties (north, east, south, west) that indicate
21/// whether the fence connects in that direction. A fence connects to:
22/// - Other fences of the same type
23/// - Fence gates facing the appropriate direction
24/// - Blocks with a sturdy face on the connecting side
25#[block_behavior]
26pub struct FenceBlock {
27    block: BlockRef,
28}
29
30impl FenceBlock {
31    /// North connection property.
32    pub const NORTH: BoolProperty = BlockStateProperties::NORTH;
33    /// East connection property.
34    pub const EAST: BoolProperty = BlockStateProperties::EAST;
35    /// South connection property.
36    pub const SOUTH: BoolProperty = BlockStateProperties::SOUTH;
37    /// West connection property.
38    pub const WEST: BoolProperty = BlockStateProperties::WEST;
39    /// Waterlogged property.
40    pub const WATERLOGGED: BoolProperty = BlockStateProperties::WATERLOGGED;
41
42    /// Creates a new fence block behavior for the given block.
43    #[must_use]
44    pub const fn new(block: BlockRef) -> Self {
45        Self { block }
46    }
47
48    /// Checks if this fence should connect to the given neighbor state.
49    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        // Check if it's a fence (same tag)
58        if neighbor_block.has_tag(&BlockTag::FENCES) {
59            return true;
60        }
61
62        // Check if it's a fence gate facing the right direction
63        if neighbor_block.has_tag(&BlockTag::FENCE_GATES) {
64            // Fence gates connect perpendicular to their facing direction
65            // A gate facing north/south connects to fences to its east/west
66            // A gate facing east/west connects to fences to its north/south
67            if let Some(gate_facing) =
68                neighbor_state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING)
69            {
70                // Gate connects perpendicular to its facing
71                let connects = match (gate_facing, direction) {
72                    // Gate facing N/S connects to blocks on E/W sides,
73                    // Gate facing E/W connects to blocks on N/S sides
74                    (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        // Check if the neighbor has a sturdy face on the opposite side
87        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    /// Gets the connection state for a position by checking all 4 horizontal neighbors.
99    fn get_connection_state(&self, world: &Arc<World>, pos: BlockPos) -> BlockStateId {
100        let mut state = self.block.default_state();
101
102        // Check north
103        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        // Check east
109        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        // Check south
115        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        // Check west
121        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        // Only update for horizontal directions
158        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            // Vertical directions don't affect fence connections
180            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}