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