Skip to main content

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

1//! Vanilla weighted 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, 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/// Vanilla weighted pressure plate with analog output based on entity count.
21#[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    /// Creates a weighted pressure plate from extracted vanilla data.
36    #[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}