1use steel_macros::block_behavior;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt;
9use steel_registry::blocks::properties::{
10 BlockStateProperties, BoolProperty, Direction, EnumProperty, WallSide,
11};
12use steel_registry::blocks::shapes::{OffsetVoxelShape, offset_face_rectangles_cover};
13use steel_registry::vanilla_block_tags::BlockTag;
14use steel_registry::vanilla_fluids;
15use steel_registry::vanilla_fluids::WATER;
16use steel_utils::{BlockPos, BlockStateId};
17
18use crate::behavior::block::BlockBehavior;
19use crate::behavior::blocks::building::FenceGateBlock;
20use crate::behavior::blocks::utils::is_excluded_for_connection;
21use crate::behavior::context::BlockPlaceContext;
22use crate::entity::ai::path::PathComputationType;
23use crate::world::{LevelReader as _, ScheduledTickAccess};
24
25#[block_behavior]
34pub struct WallBlock {
35 block: BlockRef,
36}
37
38const UP: BoolProperty = BlockStateProperties::UP;
40const NORTH: EnumProperty<WallSide> = BlockStateProperties::NORTH_WALL;
42const EAST: EnumProperty<WallSide> = BlockStateProperties::EAST_WALL;
44const SOUTH: EnumProperty<WallSide> = BlockStateProperties::SOUTH_WALL;
46const WEST: EnumProperty<WallSide> = BlockStateProperties::WEST_WALL;
48const WATERLOGGED: BoolProperty = BlockStateProperties::WATERLOGGED;
50
51const POST_X_MIN: f64 = 7.0 / 16.0;
53const POST_X_MAX: f64 = 9.0 / 16.0;
54const POST_Z_MIN: f64 = 7.0 / 16.0;
55const POST_Z_MAX: f64 = 9.0 / 16.0;
56
57const WALL_ARM_MIN: f64 = 7.0 / 16.0;
60const WALL_ARM_MAX: f64 = 9.0 / 16.0;
61const WALL_ARM_EXTENT: f64 = 9.0 / 16.0;
62
63impl WallBlock {
64 #[must_use]
66 pub const fn new(block: BlockRef) -> Self {
67 Self { block }
68 }
69}
70
71impl BlockBehavior for WallBlock {
72 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
73 let world = context.world;
74 let pos = context.place_pos();
75
76 let north_pos = Direction::North.relative(pos);
77 let east_pos = Direction::East.relative(pos);
78 let south_pos = Direction::South.relative(pos);
79 let west_pos = Direction::West.relative(pos);
80 let top_pos = Direction::Up.relative(pos);
81
82 let north_state = world.get_block_state(north_pos);
83 let east_state = world.get_block_state(east_pos);
84 let south_state = world.get_block_state(south_pos);
85 let west_state = world.get_block_state(west_pos);
86 let top_state = world.get_block_state(top_pos);
87
88 let north = connects_to(
91 north_state,
92 world.is_face_sturdy(north_state, north_pos, Direction::South),
93 Direction::South,
94 );
95 let east = connects_to(
96 east_state,
97 world.is_face_sturdy(east_state, east_pos, Direction::West),
98 Direction::West,
99 );
100 let south = connects_to(
101 south_state,
102 world.is_face_sturdy(south_state, south_pos, Direction::North),
103 Direction::North,
104 );
105 let west = connects_to(
106 west_state,
107 world.is_face_sturdy(west_state, west_pos, Direction::East),
108 Direction::East,
109 );
110
111 let state = self
112 .block
113 .default_state()
114 .set_value(&WATERLOGGED, context.is_water_source());
115
116 Some(update_wall_state(
117 state, top_pos, top_state, north, east, south, west,
118 ))
119 }
120
121 fn update_shape(
122 &self,
123 state: BlockStateId,
124 world: &dyn ScheduledTickAccess,
125 pos: BlockPos,
126 direction: Direction,
127 neighbor_pos: BlockPos,
128 neighbor_state: BlockStateId,
129 ) -> BlockStateId {
130 if state.get_value(&WATERLOGGED) {
131 let water = &vanilla_fluids::WATER;
132 world.schedule_fluid_tick_default(pos, water, world.fluid_tick_delay(&WATER));
133 }
134
135 match direction {
136 Direction::Down => state,
138 Direction::Up => top_update(state, neighbor_pos, neighbor_state),
139 _ => side_update(world, pos, state, neighbor_pos, neighbor_state, direction),
140 }
141 }
142
143 fn is_pathfindable(
144 &self,
145 _state: BlockStateId,
146 _computation_type: PathComputationType,
147 ) -> bool {
148 false
149 }
150}
151
152fn is_connected(state: BlockStateId, side: &EnumProperty<WallSide>) -> bool {
154 state.get_value(side) != WallSide::None
155}
156
157fn connects_to(neighbor_state: BlockStateId, face_solid: bool, direction: Direction) -> bool {
162 let block = neighbor_state.get_block();
163 let connected_fence_gate = block.has_tag(&BlockTag::FENCE_GATES)
164 && FenceGateBlock::connects_to_direction(neighbor_state, direction);
165
166 block.has_tag(&BlockTag::WALLS)
167 || (!is_excluded_for_connection(block) && face_solid)
168 || block.has_tag(&BlockTag::BARS)
169 || block.has_tag(&BlockTag::C_GLASS_PANES)
170 || connected_fence_gate
171}
172
173fn top_update(state: BlockStateId, top_pos: BlockPos, top_neighbor: BlockStateId) -> BlockStateId {
175 let north = is_connected(state, &NORTH);
176 let east = is_connected(state, &EAST);
177 let south = is_connected(state, &SOUTH);
178 let west = is_connected(state, &WEST);
179 update_wall_state(state, top_pos, top_neighbor, north, east, south, west)
180}
181
182fn side_update(
184 world: &dyn ScheduledTickAccess,
185 pos: BlockPos,
186 state: BlockStateId,
187 neighbor_pos: BlockPos,
188 neighbor: BlockStateId,
189 direction: Direction,
190) -> BlockStateId {
191 let opposite = direction.opposite();
192 let connected = connects_to(
193 neighbor,
194 world.is_face_sturdy(neighbor, neighbor_pos, opposite),
195 opposite,
196 );
197
198 let north = if direction == Direction::North {
199 connected
200 } else {
201 is_connected(state, &NORTH)
202 };
203 let east = if direction == Direction::East {
204 connected
205 } else {
206 is_connected(state, &EAST)
207 };
208 let south = if direction == Direction::South {
209 connected
210 } else {
211 is_connected(state, &SOUTH)
212 };
213 let west = if direction == Direction::West {
214 connected
215 } else {
216 is_connected(state, &WEST)
217 };
218
219 let above = Direction::Up.relative(pos);
220 let above_state = world.get_block_state(above);
221 update_wall_state(state, above, above_state, north, east, south, west)
222}
223
224#[expect(
226 clippy::fn_params_excessive_bools,
227 reason = "mirrors vanilla WallBlock north/east/south/west signature"
228)]
229fn update_wall_state(
230 state: BlockStateId,
231 top_pos: BlockPos,
232 top_neighbor: BlockStateId,
233 north: bool,
234 east: bool,
235 south: bool,
236 west: bool,
237) -> BlockStateId {
238 let above_shape = top_neighbor.get_collision_shape_at(top_pos);
239 let sides = update_sides(state, above_shape, north, east, south, west);
240 sides.set_value(&UP, should_raise_post(sides, top_neighbor, above_shape))
241}
242
243#[expect(
245 clippy::fn_params_excessive_bools,
246 reason = "mirrors vanilla WallBlock north/east/south/west signature"
247)]
248fn update_sides(
249 state: BlockStateId,
250 above_shape: OffsetVoxelShape,
251 north: bool,
252 east: bool,
253 south: bool,
254 west: bool,
255) -> BlockStateId {
256 state
257 .set_value(
258 &NORTH,
259 make_wall_state(
260 north,
261 above_shape,
262 WALL_ARM_MIN,
263 WALL_ARM_MAX,
264 0.0,
265 WALL_ARM_EXTENT,
266 ),
267 )
268 .set_value(
269 &EAST,
270 make_wall_state(
271 east,
272 above_shape,
273 WALL_ARM_MIN,
274 1.0,
275 WALL_ARM_MIN,
276 WALL_ARM_MAX,
277 ),
278 )
279 .set_value(
280 &SOUTH,
281 make_wall_state(
282 south,
283 above_shape,
284 WALL_ARM_MIN,
285 WALL_ARM_MAX,
286 WALL_ARM_MIN,
287 1.0,
288 ),
289 )
290 .set_value(
291 &WEST,
292 make_wall_state(
293 west,
294 above_shape,
295 0.0,
296 WALL_ARM_EXTENT,
297 WALL_ARM_MIN,
298 WALL_ARM_MAX,
299 ),
300 )
301}
302
303fn make_wall_state(
305 connects_to_side: bool,
306 above_shape: OffsetVoxelShape,
307 x_min: f64,
308 x_max: f64,
309 z_min: f64,
310 z_max: f64,
311) -> WallSide {
312 if !connects_to_side {
313 return WallSide::None;
314 }
315 if is_covered(above_shape, x_min, x_max, z_min, z_max) {
316 WallSide::Tall
317 } else {
318 WallSide::Low
319 }
320}
321
322fn should_raise_post(
324 state: BlockStateId,
325 top_neighbor: BlockStateId,
326 above_shape: OffsetVoxelShape,
327) -> bool {
328 let top_neighbor_has_post = top_neighbor.get_block().has_tag(&BlockTag::WALLS)
329 && top_neighbor.try_get_value(&UP).unwrap_or(false);
330 if top_neighbor_has_post {
331 return true;
332 }
333
334 let north_wall = state.get_value(&NORTH);
335 let south_wall = state.get_value(&SOUTH);
336 let east_wall = state.get_value(&EAST);
337 let west_wall = state.get_value(&WEST);
338
339 let north_none = north_wall == WallSide::None;
340 let south_none = south_wall == WallSide::None;
341 let east_none = east_wall == WallSide::None;
342 let west_none = west_wall == WallSide::None;
343
344 let has_corner = (north_none && south_none && west_none && east_none)
345 || (north_none != south_none)
346 || (west_none != east_none);
347 if has_corner {
348 return true;
349 }
350
351 let has_high_wall = (north_wall == WallSide::Tall && south_wall == WallSide::Tall)
352 || (east_wall == WallSide::Tall && west_wall == WallSide::Tall);
353 if has_high_wall {
354 return false;
355 }
356
357 top_neighbor
358 .get_block()
359 .has_tag(&BlockTag::WALL_POST_OVERRIDE)
360 || is_covered(above_shape, POST_X_MIN, POST_X_MAX, POST_Z_MIN, POST_Z_MAX)
361}
362
363fn is_covered(
368 above_shape: OffsetVoxelShape,
369 x_min: f64,
370 x_max: f64,
371 z_min: f64,
372 z_max: f64,
373) -> bool {
374 offset_face_rectangles_cover(above_shape, Direction::Down, x_min, x_max, z_min, z_max)
375}