steel_core/behavior/blocks/redstone/pressure_plate/
standard.rs1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum PressurePlateSensitivity {
22 Everything,
24 Mobs,
26}
27
28#[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 #[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 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, ¢er, 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}