Skip to main content

steel_core/behavior/blocks/building/
slab_block.rs

1//! Slab block behavior implementation.
2//!
3//! Slabs choose top/bottom/double shape during placement and only single slabs
4//! implement vanilla waterlogging.
5
6use 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/// Behavior for slab blocks.
27#[block_behavior]
28pub struct SlabBlock {
29    block: BlockRef,
30}
31
32impl SlabBlock {
33    /// Creates a new slab block behavior for the given block.
34    #[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/// Weathering copper slabs share slab shape/fluid behavior and add copper aging.
111#[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    /// Creates a new weathering copper slab behavior.
120    #[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}