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