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