steel_core/behavior/blocks/building/
ladder_block.rs1use crate::behavior::{BlockBehavior, BlockPlaceContext};
2use crate::world::{LevelReader, ScheduledTickAccess};
3use steel_macros::block_behavior;
4use steel_registry::blocks::BlockRef;
5use steel_registry::blocks::block_state_ext::BlockStateExt;
6use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty, EnumProperty};
7use steel_registry::{vanilla_blocks, vanilla_fluids};
8use steel_utils::{BlockPos, BlockStateId, Direction};
9
10const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
12
13const FACING: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
15
16#[block_behavior]
18pub struct LadderBlock {
19 block: BlockRef,
20}
21
22impl LadderBlock {
23 #[must_use]
25 pub const fn new(block: BlockRef) -> Self {
26 Self { block }
27 }
28}
29
30impl BlockBehavior for LadderBlock {
31 fn update_shape(
32 &self,
33 state: BlockStateId,
34 world: &dyn ScheduledTickAccess,
35 pos: BlockPos,
36 direction: Direction,
37 _neighbor_pos: BlockPos,
38 _neighbor_state: BlockStateId,
39 ) -> BlockStateId {
40 let facing: Direction = state.get_value(FACING);
41
42 if direction == facing.opposite() && !self.can_survive(state, world, pos) {
43 return vanilla_blocks::AIR.default_state();
44 }
45
46 if state.get_value(WATERLOGGED) {
47 let delay = world.fluid_tick_delay(&vanilla_fluids::WATER);
48 let _ = world.schedule_fluid_tick_default(pos, &vanilla_fluids::WATER, delay);
49 }
50
51 state
52 }
53
54 fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
55 let direction = state.get_value(FACING);
56 can_attach_to(world, pos.relative(direction.opposite()), direction)
57 }
58
59 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
60 if !context.replaces_clicked_block() {
61 let state = context.world.get_block_state(
62 context
63 .place_pos()
64 .relative(context.clicked_face().opposite()),
65 );
66 if state.get_block() == self.block && state.get_value(FACING) == context.clicked_face()
67 {
68 return None;
69 }
70 }
71
72 let mut state = self.block.default_state();
73
74 for direction in context.get_nearest_looking_directions() {
75 if !direction.is_horizontal() {
76 continue;
77 }
78
79 state = state.set_value(FACING, direction.opposite());
80 if self.can_survive(state, context.world, context.place_pos()) {
81 return Some(state.set_value(WATERLOGGED, context.is_water_source()));
82 }
83 }
84
85 None
86 }
87}
88
89fn can_attach_to(world: &dyn LevelReader, pos: BlockPos, direction: Direction) -> bool {
91 let state = world.get_block_state(pos);
92 world.is_face_sturdy(state, pos, direction)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use steel_registry::fluid::FluidRef;
99 use steel_registry::init_vanilla_registry;
100
101 struct EmptyLevel;
102
103 impl LevelReader for EmptyLevel {
104 fn get_block_state(&self, _pos: BlockPos) -> BlockStateId {
105 vanilla_blocks::AIR.default_state()
106 }
107
108 fn raw_brightness(&self, _pos: BlockPos, _sky_darkening: u8) -> u8 {
109 0
110 }
111
112 fn min_y(&self) -> i32 {
113 -64
114 }
115
116 fn height(&self) -> i32 {
117 384
118 }
119 }
120
121 impl ScheduledTickAccess for EmptyLevel {
122 fn fluid_tick_delay(&self, _fluid: FluidRef) -> i32 {
123 5
124 }
125
126 fn schedule_block_tick_default(
127 &self,
128 _pos: BlockPos,
129 _block: BlockRef,
130 _delay: i32,
131 ) -> bool {
132 true
133 }
134
135 fn schedule_fluid_tick_default(
136 &self,
137 _pos: BlockPos,
138 _fluid: FluidRef,
139 _delay: i32,
140 ) -> bool {
141 true
142 }
143 }
144
145 #[test]
146 fn support_neighbor_update_breaks_unsupported_ladder() {
147 init_vanilla_registry();
148 let behavior = LadderBlock::new(&vanilla_blocks::LADDER);
149 let state = vanilla_blocks::LADDER
150 .default_state()
151 .set_value(FACING, Direction::East);
152
153 let updated = behavior.update_shape(
154 state,
155 &EmptyLevel,
156 BlockPos::ZERO,
157 Direction::West,
158 BlockPos::ZERO.relative(Direction::West),
159 vanilla_blocks::AIR.default_state(),
160 );
161
162 assert_eq!(updated, vanilla_blocks::AIR.default_state());
163 }
164
165 #[test]
166 fn waterlogged_ladder_contains_non_falling_source_water() {
167 init_vanilla_registry();
168 let state = vanilla_blocks::LADDER
169 .default_state()
170 .set_value(WATERLOGGED, true);
171
172 let fluid = state.get_fluid_state();
173
174 assert_eq!(fluid.fluid_id, &vanilla_fluids::WATER);
175 assert!(fluid.is_source());
176 assert!(!fluid.falling);
177 }
178}