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