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};
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/// Vanilla weighted pressure plate with analog output based on entity count.
20#[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    /// Creates a weighted pressure plate from extracted vanilla data.
33    #[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}