Skip to main content

steel_core/behavior/blocks/building/
bar_block.rs

1//! bar block behavior implementation.
2//!
3//! bars connect to adjacent bars, bar solid blocks.
4
5use std::sync::Arc;
6use steel_macros::block_behavior;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt;
9use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty, Direction};
10use steel_registry::vanilla_block_tags::BlockTag;
11use steel_utils::{BlockPos, BlockStateId};
12
13use crate::behavior::block::{BlockBehavior, schedule_water_tick_if_waterlogged};
14use crate::behavior::blocks::WeatherState;
15use crate::behavior::blocks::building::WeatheringCopper;
16use crate::behavior::blocks::utils::is_excluded_for_connection;
17use crate::behavior::context::BlockPlaceContext;
18use crate::entity::ai::path::PathComputationType;
19use crate::world::{LevelReader, ScheduledTickAccess, World};
20
21/// Behavior for bar blocks.
22///
23/// bars have 4 boolean properties (north, east, south, west) that indicate
24/// whether the bar connects in that direction. A bar connects to:
25/// - Other bars of the same type
26/// - bar gates facing the appropriate direction
27/// - Blocks with a sturdy face on the connecting side
28#[block_behavior]
29pub struct IronBarsBlock {
30    block: BlockRef,
31}
32
33/// North connection property.
34const NORTH: BoolProperty = BlockStateProperties::NORTH;
35/// East connection property.
36const EAST: BoolProperty = BlockStateProperties::EAST;
37/// South connection property.
38const SOUTH: BoolProperty = BlockStateProperties::SOUTH;
39/// West connection property.
40const WEST: BoolProperty = BlockStateProperties::WEST;
41/// Waterlogged property.
42const WATERLOGGED: BoolProperty = BlockStateProperties::WATERLOGGED;
43
44impl IronBarsBlock {
45    /// Creates a new bar block behavior for the given block.
46    #[must_use]
47    pub const fn new(block: BlockRef) -> Self {
48        Self { block }
49    }
50}
51
52impl BlockBehavior for IronBarsBlock {
53    fn update_shape(
54        &self,
55        state: BlockStateId,
56        world: &dyn ScheduledTickAccess,
57        pos: BlockPos,
58        direction: Direction,
59        neighbor_pos: BlockPos,
60        neighbor_state: BlockStateId,
61    ) -> BlockStateId {
62        schedule_water_tick_if_waterlogged(state, world, pos);
63        update_shape(world, state, neighbor_state, neighbor_pos, direction)
64    }
65
66    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
67        Some(
68            get_connection_state(self.block, context.world, &context.place_pos())
69                .set_value(&WATERLOGGED, context.is_water_source()),
70        )
71    }
72
73    fn is_pathfindable(
74        &self,
75        _state: BlockStateId,
76        _computation_type: PathComputationType,
77    ) -> bool {
78        false
79    }
80}
81
82/// Behavior for copper bar blocks.
83///
84/// bars have 4 boolean properties (north, east, south, west) that indicate
85/// whether the bar connects in that direction. A bar connects to:
86/// - Other bars of the same type
87/// - bar gates facing the appropriate direction
88/// - Blocks with a sturdy face on the connecting side
89#[block_behavior]
90pub struct WeatheringCopperBarsBlock {
91    block: BlockRef,
92    #[json_arg(r#enum = "WeatherState", json = "weather_state")]
93    weathering: WeatheringCopper,
94}
95
96impl WeatheringCopperBarsBlock {
97    /// Creates a new bar block behavior for the given block.
98    #[must_use]
99    pub const fn new(block: BlockRef, weather_state: WeatherState) -> Self {
100        Self {
101            block,
102            weathering: WeatheringCopper::new(weather_state),
103        }
104    }
105}
106
107impl BlockBehavior for WeatheringCopperBarsBlock {
108    fn update_shape(
109        &self,
110        state: BlockStateId,
111        world: &dyn ScheduledTickAccess,
112        pos: BlockPos,
113        direction: Direction,
114        neighbor_pos: BlockPos,
115        neighbor_state: BlockStateId,
116    ) -> BlockStateId {
117        schedule_water_tick_if_waterlogged(state, world, pos);
118        update_shape(world, state, neighbor_state, neighbor_pos, direction)
119    }
120
121    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
122        Some(
123            get_connection_state(self.block, context.world, &context.place_pos())
124                .set_value(&WATERLOGGED, context.is_water_source()),
125        )
126    }
127
128    fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
129        self.weathering.change_over_time(state, world, pos);
130    }
131
132    fn is_pathfindable(
133        &self,
134        _state: BlockStateId,
135        _computation_type: PathComputationType,
136    ) -> bool {
137        false
138    }
139}
140
141/// Checks if this bar should connect to the given neighbor state.
142fn connects_to(
143    world: &dyn LevelReader,
144    neighbor_state: BlockStateId,
145    neighbor_pos: BlockPos,
146    direction: Direction,
147) -> bool {
148    let neighbor_block = neighbor_state.get_block();
149    let excluded = is_excluded_for_connection(neighbor_block);
150    (!excluded && world.is_face_sturdy(neighbor_state, neighbor_pos, direction.opposite()))
151        || neighbor_block.has_tag(&BlockTag::BARS)
152        || neighbor_block.has_tag(&BlockTag::WALLS)
153        || neighbor_block.has_tag(&BlockTag::C_GLASS_PANES)
154}
155
156/// Gets the connection state for a position by checking all 4 horizontal neighbors.
157pub fn get_connection_state(block: BlockRef, world: &World, pos: &BlockPos) -> BlockStateId {
158    let mut state = block.default_state();
159
160    // Check north
161    let north_pos = Direction::North.relative(*pos);
162    let north_state = world.get_block_state(north_pos);
163    let connects_north = connects_to(world, north_state, north_pos, Direction::North);
164    state = state.set_value(&NORTH, connects_north);
165
166    // Check east
167    let east_pos = Direction::East.relative(*pos);
168    let east_state = world.get_block_state(east_pos);
169    let connects_east = connects_to(world, east_state, east_pos, Direction::East);
170    state = state.set_value(&EAST, connects_east);
171
172    // Check south
173    let south_pos = Direction::South.relative(*pos);
174    let south_state = world.get_block_state(south_pos);
175    let connects_south = connects_to(world, south_state, south_pos, Direction::South);
176    state = state.set_value(&SOUTH, connects_south);
177
178    // Check west
179    let west_pos = Direction::West.relative(*pos);
180    let west_state = world.get_block_state(west_pos);
181    let connects_west = connects_to(world, west_state, west_pos, Direction::West);
182    state = state.set_value(&WEST, connects_west);
183
184    state
185}
186
187pub fn update_shape(
188    world: &dyn LevelReader,
189    state: BlockStateId,
190    neighbor_state: BlockStateId,
191    neighbor_pos: BlockPos,
192    direction: Direction,
193) -> BlockStateId {
194    match direction {
195        Direction::North => {
196            let connects = connects_to(world, neighbor_state, neighbor_pos, Direction::North);
197            state.set_value(&NORTH, connects)
198        }
199        Direction::East => {
200            let connects = connects_to(world, neighbor_state, neighbor_pos, Direction::East);
201            state.set_value(&EAST, connects)
202        }
203        Direction::South => {
204            let connects = connects_to(world, neighbor_state, neighbor_pos, Direction::South);
205            state.set_value(&SOUTH, connects)
206        }
207        Direction::West => {
208            let connects = connects_to(world, neighbor_state, neighbor_pos, Direction::West);
209            state.set_value(&WEST, connects)
210        }
211        Direction::Up | Direction::Down => state,
212    }
213}