Skip to main content

steel_core/behavior/blocks/redstone/pressure_plate/
standard.rs

1//! Vanilla binary pressure-plate behavior.
2
3use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::blocks::properties::{BlockStateProperties, Direction};
9use steel_registry::sound_event::SoundEventRef;
10use steel_utils::{BlockPos, BlockStateId};
11
12use super::base::BasePressurePlateBlock;
13use crate::behavior::{BlockBehavior, BlockPlaceContext};
14use crate::entity::{Entity, InsideBlockEffectCollector};
15use crate::world::{LevelReader, ScheduledTickAccess, SignalQueryContext, World};
16
17const PRESSED_TIME: i32 = 20;
18
19/// Vanilla `BlockSetType.PressurePlateSensitivity` values.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum PressurePlateSensitivity {
22    /// Any non-spectating entity that responds to block triggers.
23    Everything,
24    /// Only entities implementing vanilla living-entity behavior.
25    Mobs,
26}
27
28/// Vanilla on/off pressure plates, including wood and stone variants.
29#[block_behavior]
30pub struct PressurePlateBlock {
31    base: BasePressurePlateBlock,
32    #[json_arg(
33        r#enum = "PressurePlateSensitivity",
34        json = "type_pressure_plate_sensitivity"
35    )]
36    sensitivity: PressurePlateSensitivity,
37    #[json_arg(sound_events, json = "type_pressure_plate_click_on")]
38    sound_click_on: SoundEventRef,
39    #[json_arg(sound_events, json = "type_pressure_plate_click_off")]
40    sound_click_off: SoundEventRef,
41}
42
43impl PressurePlateBlock {
44    /// Creates a binary pressure-plate behavior from extracted block-set data.
45    #[must_use]
46    pub const fn new(
47        block: BlockRef,
48        sensitivity: PressurePlateSensitivity,
49        sound_click_on: SoundEventRef,
50        sound_click_off: SoundEventRef,
51    ) -> Self {
52        Self {
53            base: BasePressurePlateBlock::new(block),
54            sensitivity,
55            sound_click_on,
56            sound_click_off,
57        }
58    }
59
60    fn signal_for_state(state: BlockStateId) -> i32 {
61        if state.get_value(&BlockStateProperties::POWERED) {
62            15
63        } else {
64            0
65        }
66    }
67
68    fn state_for_signal(state: BlockStateId, signal: i32) -> BlockStateId {
69        state.set_value(&BlockStateProperties::POWERED, signal > 0)
70    }
71
72    fn signal_strength(&self, world: &World, pos: BlockPos) -> i32 {
73        let count =
74            BasePressurePlateBlock::entity_count(world, pos, |entity| match self.sensitivity {
75                PressurePlateSensitivity::Everything => true,
76                // Class-hierarchy checks stay capability-based: raw fallback
77                // entities become eligible when they gain `LivingEntity` behavior.
78                PressurePlateSensitivity::Mobs => entity.is_living_entity(),
79            });
80        if count > 0 { 15 } else { 0 }
81    }
82
83    fn check_pressed(
84        &self,
85        source_entity: Option<&dyn Entity>,
86        world: &Arc<World>,
87        pos: BlockPos,
88        state: BlockStateId,
89        old_signal: i32,
90    ) {
91        let signal = self.signal_strength(world.as_ref(), pos);
92        self.base.check_pressed(
93            source_entity,
94            world,
95            pos,
96            old_signal,
97            signal,
98            Self::state_for_signal(state, signal),
99            PRESSED_TIME,
100            self.sound_click_on,
101            self.sound_click_off,
102        );
103    }
104}
105
106impl BlockBehavior for PressurePlateBlock {
107    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
108        BasePressurePlateBlock::can_survive(world, pos)
109    }
110
111    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
112        self.base.state_for_placement(context)
113    }
114
115    fn update_shape(
116        &self,
117        state: BlockStateId,
118        world: &dyn ScheduledTickAccess,
119        pos: BlockPos,
120        direction: Direction,
121        _neighbor_pos: BlockPos,
122        _neighbor_state: BlockStateId,
123    ) -> BlockStateId {
124        BasePressurePlateBlock::update_shape(state, world, pos, direction)
125    }
126
127    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
128        let signal = Self::signal_for_state(state);
129        if signal > 0 {
130            self.check_pressed(None, world, pos, state, signal);
131        }
132    }
133
134    fn entity_inside(
135        &self,
136        state: BlockStateId,
137        world: &Arc<World>,
138        pos: BlockPos,
139        entity: &dyn Entity,
140        _effect_collector: &mut InsideBlockEffectCollector,
141        _is_precise: bool,
142    ) {
143        let signal = Self::signal_for_state(state);
144        if signal == 0 {
145            self.check_pressed(Some(entity), world, pos, state, signal);
146        }
147    }
148
149    fn affect_neighbors_after_removal(
150        &self,
151        state: BlockStateId,
152        world: &Arc<World>,
153        pos: BlockPos,
154        moved_by_piston: bool,
155    ) {
156        self.base.affect_neighbors_after_removal(
157            world,
158            pos,
159            moved_by_piston,
160            Self::signal_for_state(state),
161        );
162    }
163
164    fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
165        true
166    }
167
168    fn get_own_signal(
169        &self,
170        state: BlockStateId,
171        _world: &dyn LevelReader,
172        _pos: BlockPos,
173        _context: SignalQueryContext,
174    ) -> i32 {
175        Self::signal_for_state(state)
176    }
177
178    fn get_direct_signal(
179        &self,
180        state: BlockStateId,
181        _world: &dyn LevelReader,
182        _pos: BlockPos,
183        direction: Direction,
184        _context: SignalQueryContext,
185    ) -> i32 {
186        if direction == Direction::Up {
187            Self::signal_for_state(state)
188        } else {
189            0
190        }
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use steel_registry::{init_vanilla_registry, sound_events, vanilla_blocks};
197
198    use super::*;
199    use crate::test_support::TestLevel;
200
201    fn stone_pressure_plate() -> PressurePlateBlock {
202        PressurePlateBlock::new(
203            &vanilla_blocks::STONE_PRESSURE_PLATE,
204            PressurePlateSensitivity::Mobs,
205            &sound_events::BLOCK_STONE_PRESSURE_PLATE_CLICK_ON,
206            &sound_events::BLOCK_STONE_PRESSURE_PLATE_CLICK_OFF,
207        )
208    }
209
210    #[test]
211    fn pressure_plate_survives_on_rigid_or_center_support() {
212        init_vanilla_registry();
213        let behavior = stone_pressure_plate();
214        let pos = BlockPos::new(0, 64, 0);
215        let state = vanilla_blocks::STONE_PRESSURE_PLATE.default_state();
216        let rigid =
217            TestLevel::default().with_block(pos.below(), vanilla_blocks::STONE.default_state());
218        let center =
219            TestLevel::default().with_block(pos.below(), vanilla_blocks::OAK_FENCE.default_state());
220
221        assert!(behavior.can_survive(state, &rigid, pos));
222        assert!(behavior.can_survive(state, &center, pos));
223        assert!(!behavior.can_survive(state, &TestLevel::default(), pos));
224    }
225
226    #[test]
227    fn powered_pressure_plate_strongly_powers_only_upward() {
228        init_vanilla_registry();
229        let behavior = stone_pressure_plate();
230        let state = vanilla_blocks::STONE_PRESSURE_PLATE
231            .default_state()
232            .set_value(&BlockStateProperties::POWERED, true);
233        let level = TestLevel::default();
234
235        assert_eq!(
236            behavior.get_own_signal(state, &level, BlockPos::ZERO, SignalQueryContext::DEFAULT,),
237            15
238        );
239        assert_eq!(
240            behavior.get_direct_signal(
241                state,
242                &level,
243                BlockPos::ZERO,
244                Direction::Up,
245                SignalQueryContext::DEFAULT,
246            ),
247            15
248        );
249        assert_eq!(
250            behavior.get_direct_signal(
251                state,
252                &level,
253                BlockPos::ZERO,
254                Direction::North,
255                SignalQueryContext::DEFAULT,
256            ),
257            0
258        );
259    }
260}