Skip to main content

steel_core/behavior/blocks/redstone/diode/
base.rs

1//! Shared vanilla `DiodeBlock` behavior for repeaters and comparators.
2
3use std::sync::Arc;
4
5use steel_registry::blocks::BlockRef;
6use steel_registry::blocks::block_state_ext::BlockStateExt as _;
7use steel_registry::blocks::properties::{BlockStateProperties, Direction};
8use steel_registry::blocks::shapes::SupportType;
9use steel_registry::vanilla_blocks;
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId};
12
13use crate::behavior::BLOCK_BEHAVIORS;
14use crate::behavior::BlockPlaceContext;
15use crate::world::{
16    LevelReader, SignalQueryContext, World, get_control_input_signal,
17    get_signal as get_redstone_signal, tick_scheduler::TickPriority,
18};
19
20/// Common server-side behavior inherited from vanilla's abstract `DiodeBlock`.
21pub(super) struct DiodeBlock {
22    pub(super) block: BlockRef,
23}
24
25impl DiodeBlock {
26    #[must_use]
27    pub(super) const fn new(block: BlockRef) -> Self {
28        Self { block }
29    }
30
31    pub(super) fn can_survive_on(
32        level: &dyn LevelReader,
33        neighbor_pos: BlockPos,
34        neighbor_state: BlockStateId,
35    ) -> bool {
36        level.is_face_sturdy_for(
37            neighbor_state,
38            neighbor_pos,
39            Direction::Up,
40            SupportType::Rigid,
41        )
42    }
43
44    pub(super) fn can_survive(level: &dyn LevelReader, pos: BlockPos) -> bool {
45        let below_pos = pos.below();
46        Self::can_survive_on(level, below_pos, level.get_block_state(below_pos))
47    }
48
49    pub(super) fn state_for_placement(&self, context: &BlockPlaceContext<'_>) -> BlockStateId {
50        self.block.default_state().set_value(
51            &BlockStateProperties::HORIZONTAL_FACING,
52            context.horizontal_direction().opposite(),
53        )
54    }
55
56    pub(super) fn get_input_signal(
57        level: &dyn LevelReader,
58        pos: BlockPos,
59        state: BlockStateId,
60    ) -> i32 {
61        let direction = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
62        let target_pos = pos.relative(direction);
63        let input = get_redstone_signal(level, target_pos, direction, SignalQueryContext::DEFAULT);
64        if input >= 15 {
65            return input;
66        }
67
68        let target_state = level.get_block_state(target_pos);
69        let wire_power = if target_state.get_block() == &vanilla_blocks::REDSTONE_WIRE {
70            i32::from(target_state.get_value(&BlockStateProperties::POWER))
71        } else {
72            0
73        };
74        input.max(wire_power)
75    }
76
77    pub(super) fn get_alternate_signal(
78        level: &dyn LevelReader,
79        pos: BlockPos,
80        state: BlockStateId,
81        side_input_diodes_only: bool,
82    ) -> i32 {
83        let direction = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
84        let clockwise = direction.rotate_y_clockwise();
85        let counter_clockwise = direction.rotate_y_counter_clockwise();
86        get_control_input_signal(
87            level,
88            pos.relative(clockwise),
89            clockwise,
90            side_input_diodes_only,
91        )
92        .max(get_control_input_signal(
93            level,
94            pos.relative(counter_clockwise),
95            counter_clockwise,
96            side_input_diodes_only,
97        ))
98    }
99
100    pub(super) fn should_prioritize(
101        level: &dyn LevelReader,
102        pos: BlockPos,
103        state: BlockStateId,
104    ) -> bool {
105        let direction = state
106            .get_value(&BlockStateProperties::HORIZONTAL_FACING)
107            .opposite();
108        let opposite_state = level.get_block_state(pos.relative(direction));
109        BLOCK_BEHAVIORS
110            .get_behavior(opposite_state.get_block())
111            .is_diode()
112            && opposite_state.get_value(&BlockStateProperties::HORIZONTAL_FACING) != direction
113    }
114
115    pub(super) fn check_tick_on_neighbor(
116        &self,
117        world: &Arc<World>,
118        pos: BlockPos,
119        state: BlockStateId,
120        is_locked: bool,
121        should_turn_on: bool,
122        delay: i32,
123    ) {
124        if is_locked {
125            return;
126        }
127
128        let powered = state.get_value(&BlockStateProperties::POWERED);
129        if powered == should_turn_on || world.will_tick_block_this_tick(pos, self.block) {
130            return;
131        }
132
133        let priority = Self::tick_priority(world.as_ref(), pos, state, powered);
134        world.schedule_block_tick(pos, self.block, delay, priority);
135    }
136
137    pub(super) fn tick_priority(
138        level: &dyn LevelReader,
139        pos: BlockPos,
140        state: BlockStateId,
141        powered: bool,
142    ) -> TickPriority {
143        if Self::should_prioritize(level, pos, state) {
144            TickPriority::ExtremelyHigh
145        } else if powered {
146            TickPriority::VeryHigh
147        } else {
148            TickPriority::High
149        }
150    }
151
152    pub(super) fn tick(
153        &self,
154        state: BlockStateId,
155        world: &Arc<World>,
156        pos: BlockPos,
157        is_locked: bool,
158        should_turn_on: bool,
159        delay: i32,
160    ) {
161        if is_locked {
162            return;
163        }
164
165        let powered = state.get_value(&BlockStateProperties::POWERED);
166        if powered && !should_turn_on {
167            world.set_block(
168                pos,
169                state.set_value(&BlockStateProperties::POWERED, false),
170                UpdateFlags::UPDATE_CLIENTS,
171            );
172        } else if !powered {
173            world.set_block(
174                pos,
175                state.set_value(&BlockStateProperties::POWERED, true),
176                UpdateFlags::UPDATE_CLIENTS,
177            );
178            if !should_turn_on {
179                world.schedule_block_tick(pos, self.block, delay, TickPriority::VeryHigh);
180            }
181        }
182    }
183
184    pub(super) fn handle_neighbor_changed(
185        &self,
186        state: BlockStateId,
187        world: &Arc<World>,
188        pos: BlockPos,
189        on_supported: impl FnOnce(),
190    ) {
191        if world.get_block_state(pos).get_block() != self.block {
192            return;
193        }
194        if Self::can_survive(world.as_ref(), pos) {
195            on_supported();
196            return;
197        }
198
199        world.drop_resources(state, pos);
200        world.remove_block(pos, false);
201        for direction in Direction::ALL {
202            world.update_neighbors_at(pos.relative(direction), self.block);
203        }
204    }
205
206    pub(super) fn set_placed_by(&self, world: &Arc<World>, pos: BlockPos, should_turn_on: bool) {
207        if should_turn_on {
208            world.schedule_block_tick_default(pos, self.block, 1);
209        }
210    }
211
212    pub(super) fn on_place(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
213        self.update_neighbors_in_front(world, pos, state);
214    }
215
216    pub(super) fn affect_neighbors_after_removal(
217        &self,
218        state: BlockStateId,
219        world: &Arc<World>,
220        pos: BlockPos,
221        moved_by_piston: bool,
222    ) {
223        if !moved_by_piston {
224            self.update_neighbors_in_front(world, pos, state);
225        }
226    }
227
228    pub(super) fn update_neighbors_in_front(
229        &self,
230        world: &Arc<World>,
231        pos: BlockPos,
232        state: BlockStateId,
233    ) {
234        let direction = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
235        let opposite_pos = pos.relative(direction.opposite());
236        world.neighbor_changed(opposite_pos, self.block);
237        world.update_neighbors_at_except_from_facing(opposite_pos, self.block, direction);
238    }
239
240    pub(super) fn own_signal(state: BlockStateId, output_signal: i32) -> i32 {
241        if state.get_value(&BlockStateProperties::POWERED) {
242            output_signal
243        } else {
244            0
245        }
246    }
247
248    pub(super) fn signal(state: BlockStateId, direction: Direction, output_signal: i32) -> i32 {
249        if state.get_value(&BlockStateProperties::HORIZONTAL_FACING) == direction {
250            Self::own_signal(state, output_signal)
251        } else {
252            0
253        }
254    }
255}