steel_core/behavior/blocks/building/
slab_block.rs1use std::sync::Arc;
7
8use steel_macros::block_behavior;
9use steel_registry::{
10 blocks::{
11 BlockRef,
12 block_state_ext::BlockStateExt as _,
13 properties::{BlockStateProperties, BoolProperty, Direction, EnumProperty, SlabType},
14 },
15 fluid::{FluidRef, FluidState},
16 vanilla_fluids,
17};
18use steel_utils::{BlockPos, BlockStateId};
19
20use super::weathering_block::{WeatherState, WeatheringCopper};
21use crate::{
22 behavior::{
23 BlockBehavior, BlockPlaceContext,
24 block::{place_simple_waterlogged_liquid, schedule_water_tick_if_waterlogged},
25 },
26 world::{LevelAccessor, ScheduledTickAccess, World},
27};
28
29#[block_behavior]
31pub struct SlabBlock {
32 block: BlockRef,
33}
34
35const SLAB_TYPE: &EnumProperty<SlabType> = &BlockStateProperties::SLAB_TYPE;
36const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
37
38impl SlabBlock {
39 #[must_use]
41 pub const fn new(block: BlockRef) -> Self {
42 Self { block }
43 }
44
45 fn single_slab_type_for_placement(context: &BlockPlaceContext<'_>) -> SlabType {
46 if context.clicked_face() != Direction::Down
47 && (context.clicked_face() == Direction::Up
48 || context.click_location().y - f64::from(context.place_pos().y()) <= 0.5)
49 {
50 SlabType::Bottom
51 } else {
52 SlabType::Top
53 }
54 }
55}
56
57impl BlockBehavior for SlabBlock {
58 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
59 let existing_state = context.world.get_block_state(context.place_pos());
60 if existing_state.get_block() == self.block {
61 return Some(
62 existing_state
63 .set_value(SLAB_TYPE, SlabType::Double)
64 .set_value(WATERLOGGED, false),
65 );
66 }
67
68 Some(
69 self.block
70 .default_state()
71 .set_value(SLAB_TYPE, Self::single_slab_type_for_placement(context))
72 .set_value(WATERLOGGED, context.is_water_source()),
73 )
74 }
75
76 fn update_shape(
77 &self,
78 state: BlockStateId,
79 world: &dyn ScheduledTickAccess,
80 pos: BlockPos,
81 _direction: Direction,
82 _neighbor_pos: BlockPos,
83 _neighbor_state: BlockStateId,
84 ) -> BlockStateId {
85 schedule_water_tick_if_waterlogged(state, world, pos);
86
87 state
88 }
89
90 fn can_place_liquid(&self, state: BlockStateId, fluid: FluidRef) -> bool {
91 state.try_get_value(SLAB_TYPE) != Some(SlabType::Double) && fluid == &vanilla_fluids::WATER
92 }
93
94 fn place_liquid(
95 &self,
96 level: &dyn LevelAccessor,
97 pos: BlockPos,
98 state: BlockStateId,
99 fluid_state: FluidState,
100 ) -> bool {
101 state.try_get_value(SLAB_TYPE) != Some(SlabType::Double)
102 && place_simple_waterlogged_liquid(level, pos, state, fluid_state)
103 }
104}
105
106#[block_behavior]
108pub struct WeatheringCopperSlabBlock {
109 slab: SlabBlock,
110 #[json_arg(r#enum = "WeatherState", json = "weather_state")]
111 weathering: WeatheringCopper,
112}
113
114impl WeatheringCopperSlabBlock {
115 #[must_use]
117 pub const fn new(block: BlockRef, weather_state: WeatherState) -> Self {
118 Self {
119 slab: SlabBlock::new(block),
120 weathering: WeatheringCopper::new(weather_state),
121 }
122 }
123}
124
125impl BlockBehavior for WeatheringCopperSlabBlock {
126 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
127 self.slab.get_state_for_placement(context)
128 }
129
130 fn update_shape(
131 &self,
132 state: BlockStateId,
133 world: &dyn ScheduledTickAccess,
134 pos: BlockPos,
135 direction: Direction,
136 neighbor_pos: BlockPos,
137 neighbor_state: BlockStateId,
138 ) -> BlockStateId {
139 self.slab
140 .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
141 }
142
143 fn can_place_liquid(&self, state: BlockStateId, fluid: FluidRef) -> bool {
144 self.slab.can_place_liquid(state, fluid)
145 }
146
147 fn place_liquid(
148 &self,
149 level: &dyn LevelAccessor,
150 pos: BlockPos,
151 state: BlockStateId,
152 fluid_state: FluidState,
153 ) -> bool {
154 self.slab.place_liquid(level, pos, state, fluid_state)
155 }
156
157 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
158 self.weathering.change_over_time(state, world, pos);
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use steel_registry::{init_vanilla_registry, vanilla_blocks};
165
166 use super::*;
167
168 #[test]
169 fn double_slabs_cannot_be_waterlogged() {
170 init_vanilla_registry();
171 let behavior = SlabBlock::new(&vanilla_blocks::SMOOTH_STONE_SLAB);
172 let double_slab = vanilla_blocks::SMOOTH_STONE_SLAB
173 .default_state()
174 .set_value(SLAB_TYPE, SlabType::Double)
175 .set_value(WATERLOGGED, false);
176
177 assert!(!behavior.can_place_liquid(double_slab, &vanilla_fluids::WATER));
178 }
179
180 #[test]
181 fn single_slabs_accept_source_water_for_container_admission() {
182 init_vanilla_registry();
183 let behavior = SlabBlock::new(&vanilla_blocks::SMOOTH_STONE_SLAB);
184 let bottom_slab = vanilla_blocks::SMOOTH_STONE_SLAB
185 .default_state()
186 .set_value(SLAB_TYPE, SlabType::Bottom)
187 .set_value(WATERLOGGED, false);
188
189 assert!(behavior.can_place_liquid(bottom_slab, &vanilla_fluids::WATER));
190 }
191
192 #[test]
193 fn single_slabs_reject_flowing_water_for_container_admission() {
194 init_vanilla_registry();
195 let behavior = SlabBlock::new(&vanilla_blocks::SMOOTH_STONE_SLAB);
196 let bottom_slab = vanilla_blocks::SMOOTH_STONE_SLAB
197 .default_state()
198 .set_value(SLAB_TYPE, SlabType::Bottom)
199 .set_value(WATERLOGGED, false);
200
201 assert!(!behavior.can_place_liquid(bottom_slab, &vanilla_fluids::FLOWING_WATER));
202 }
203}