steel_core/behavior/blocks/redstone/pressure_plate/
weighted.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 = 10;
18
19#[block_behavior]
21pub struct WeightedPressurePlateBlock {
22 base: BasePressurePlateBlock,
23 #[json_arg(value)]
24 max_weight: i32,
25 #[json_arg(sound_events, json = "type_pressure_plate_click_on")]
26 sound_click_on: SoundEventRef,
27 #[json_arg(sound_events, json = "type_pressure_plate_click_off")]
28 sound_click_off: SoundEventRef,
29}
30
31impl WeightedPressurePlateBlock {
32 #[must_use]
34 pub const fn new(
35 block: BlockRef,
36 max_weight: i32,
37 sound_click_on: SoundEventRef,
38 sound_click_off: SoundEventRef,
39 ) -> Self {
40 Self {
41 base: BasePressurePlateBlock::new(block),
42 max_weight,
43 sound_click_on,
44 sound_click_off,
45 }
46 }
47
48 fn signal_for_state(state: BlockStateId) -> i32 {
49 i32::from(state.get_value(&BlockStateProperties::POWER))
50 }
51
52 fn state_for_signal(state: BlockStateId, signal: i32) -> BlockStateId {
53 state.set_value(&BlockStateProperties::POWER, signal as u8)
54 }
55
56 fn signal_for_count(count: i32, max_weight: i32) -> i32 {
57 let count = count.min(max_weight);
58 if count <= 0 {
59 return 0;
60 }
61 (((count as f32) / (max_weight as f32)) * 15.0).ceil() as i32
62 }
63
64 fn signal_strength(&self, world: &World, pos: BlockPos) -> i32 {
65 let count = BasePressurePlateBlock::entity_count(world, pos, |_| true);
66 let count = i32::try_from(count).unwrap_or(i32::MAX);
67 Self::signal_for_count(count, self.max_weight)
68 }
69
70 fn check_pressed(
71 &self,
72 source_entity: Option<&dyn Entity>,
73 world: &Arc<World>,
74 pos: BlockPos,
75 state: BlockStateId,
76 old_signal: i32,
77 ) {
78 let signal = self.signal_strength(world.as_ref(), pos);
79 self.base.check_pressed(
80 source_entity,
81 world,
82 pos,
83 old_signal,
84 signal,
85 Self::state_for_signal(state, signal),
86 PRESSED_TIME,
87 self.sound_click_on,
88 self.sound_click_off,
89 );
90 }
91}
92
93impl BlockBehavior for WeightedPressurePlateBlock {
94 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
95 BasePressurePlateBlock::can_survive(world, pos)
96 }
97
98 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
99 self.base.state_for_placement(context)
100 }
101
102 fn update_shape(
103 &self,
104 state: BlockStateId,
105 world: &dyn ScheduledTickAccess,
106 pos: BlockPos,
107 direction: Direction,
108 _neighbor_pos: BlockPos,
109 _neighbor_state: BlockStateId,
110 ) -> BlockStateId {
111 BasePressurePlateBlock::update_shape(state, world, pos, direction)
112 }
113
114 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
115 let signal = Self::signal_for_state(state);
116 if signal > 0 {
117 self.check_pressed(None, world, pos, state, signal);
118 }
119 }
120
121 fn entity_inside(
122 &self,
123 state: BlockStateId,
124 world: &Arc<World>,
125 pos: BlockPos,
126 entity: &dyn Entity,
127 _effect_collector: &mut InsideBlockEffectCollector,
128 _is_precise: bool,
129 ) {
130 let signal = Self::signal_for_state(state);
131 if signal == 0 {
132 self.check_pressed(Some(entity), world, pos, state, signal);
133 }
134 }
135
136 fn affect_neighbors_after_removal(
137 &self,
138 state: BlockStateId,
139 world: &Arc<World>,
140 pos: BlockPos,
141 moved_by_piston: bool,
142 ) {
143 self.base.affect_neighbors_after_removal(
144 world,
145 pos,
146 moved_by_piston,
147 Self::signal_for_state(state),
148 );
149 }
150
151 fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
152 true
153 }
154
155 fn get_own_signal(
156 &self,
157 state: BlockStateId,
158 _world: &dyn LevelReader,
159 _pos: BlockPos,
160 _context: SignalQueryContext,
161 ) -> i32 {
162 Self::signal_for_state(state)
163 }
164
165 fn get_direct_signal(
166 &self,
167 state: BlockStateId,
168 _world: &dyn LevelReader,
169 _pos: BlockPos,
170 direction: Direction,
171 _context: SignalQueryContext,
172 ) -> i32 {
173 if direction == Direction::Up {
174 Self::signal_for_state(state)
175 } else {
176 0
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn heavy_plate_uses_vanilla_float_ceiling_boundaries() {
187 assert_eq!(WeightedPressurePlateBlock::signal_for_count(0, 150), 0);
188 assert_eq!(WeightedPressurePlateBlock::signal_for_count(1, 150), 1);
189 assert_eq!(WeightedPressurePlateBlock::signal_for_count(10, 150), 1);
190 assert_eq!(WeightedPressurePlateBlock::signal_for_count(11, 150), 2);
191 assert_eq!(WeightedPressurePlateBlock::signal_for_count(150, 150), 15);
192 assert_eq!(WeightedPressurePlateBlock::signal_for_count(200, 150), 15);
193 }
194}