steel_core/behavior/blocks/redstone/diode/
base.rs1use 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
20pub(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}