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, 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/// Behavior for slab blocks.
30#[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    /// Creates a new slab block behavior for the given block.
40    #[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/// Weathering copper slabs share slab shape/fluid behavior and add copper aging.
107#[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    /// Creates a new weathering copper slab behavior.
116    #[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}