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 }
89
90fn can_attach_to(world: &dyn LevelReader, pos: BlockPos, direction: Direction) -> bool {
92 let state = world.get_block_state(pos);
93 world.is_face_sturdy(state, pos, direction)
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use steel_registry::fluid::FluidRef;
100 use steel_registry::init_vanilla_registry;
101
102 struct EmptyLevel;
103
104 impl LevelReader for EmptyLevel {
105 fn get_block_state(&self, _pos: BlockPos) -> BlockStateId {
106 vanilla_blocks::AIR.default_state()
107 }
108
109 fn raw_brightness(&self, _pos: BlockPos, _sky_darkening: u8) -> u8 {
110 0
111 }
112
113 fn min_y(&self) -> i32 {
114 -64
115 }
116
117 fn height(&self) -> i32 {
118 384
119 }
120 }
121
122 impl ScheduledTickAccess for EmptyLevel {
123 fn fluid_tick_delay(&self, _fluid: FluidRef) -> i32 {
124 5
125 }
126
127 fn schedule_block_tick_default(
128 &self,
129 _pos: BlockPos,
130 _block: BlockRef,
131 _delay: i32,
132 ) -> bool {
133 true
134 }
135
136 fn schedule_fluid_tick_default(
137 &self,
138 _pos: BlockPos,
139 _fluid: FluidRef,
140 _delay: i32,
141 ) -> bool {
142 true
143 }
144 }
145
146 #[test]
147 fn support_neighbor_update_breaks_unsupported_ladder() {
148 init_vanilla_registry();
149 let behavior = LadderBlock::new(&vanilla_blocks::LADDER);
150 let state = vanilla_blocks::LADDER
151 .default_state()
152 .set_value(&FACING, Direction::East);
153
154 let updated = behavior.update_shape(
155 state,
156 &EmptyLevel,
157 BlockPos::ZERO,
158 Direction::West,
159 BlockPos::ZERO.relative(Direction::West),
160 vanilla_blocks::AIR.default_state(),
161 );
162
163 assert_eq!(updated, vanilla_blocks::AIR.default_state());
164 }
165
166 #[test]
167 fn waterlogged_ladder_contains_non_falling_source_water() {
168 init_vanilla_registry();
169 let state = vanilla_blocks::LADDER
170 .default_state()
171 .set_value(&WATERLOGGED, true);
172
173 let fluid = state.get_fluid_state();
174
175 assert_eq!(fluid.fluid_id, &vanilla_fluids::WATER);
176 assert!(fluid.is_source());
177 assert!(!fluid.falling);
178 }
179}