1use steel_registry::blocks::block_state_ext::BlockStateExt as _;
4use steel_registry::blocks::properties::BlockStateProperties;
5use steel_registry::vanilla_blocks;
6use steel_utils::{BlockPos, BlockStateId, Direction};
7
8use super::LevelReader;
9use crate::behavior::BLOCK_BEHAVIORS;
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct SignalQueryContext {
18 wire_signals_enabled: bool,
19}
20
21impl SignalQueryContext {
22 pub(crate) const DEFAULT: Self = Self {
23 wire_signals_enabled: true,
24 };
25
26 #[must_use]
28 pub const fn wire_signals_enabled(self) -> bool {
29 self.wire_signals_enabled
30 }
31
32 pub(crate) const fn without_wire_signals() -> Self {
37 Self {
38 wire_signals_enabled: false,
39 }
40 }
41}
42
43pub trait SignalGetter: LevelReader {
45 fn is_redstone_conductor(&self, state: BlockStateId, pos: BlockPos) -> bool;
47
48 fn get_direct_signal(&self, pos: BlockPos, direction: Direction) -> i32;
50
51 fn get_direct_signal_to(&self, pos: BlockPos) -> i32;
53
54 fn get_control_input_signal(
56 &self,
57 pos: BlockPos,
58 direction: Direction,
59 only_diodes: bool,
60 ) -> i32;
61
62 fn has_signal(&self, pos: BlockPos, direction: Direction) -> bool;
64
65 fn get_signal(&self, pos: BlockPos, direction: Direction) -> i32;
67
68 fn get_best_own_or_neighbour_signal(&self, pos: BlockPos) -> i32;
70
71 fn has_neighbor_signal(&self, pos: BlockPos) -> bool;
73
74 fn get_best_neighbor_signal(&self, pos: BlockPos) -> i32;
76}
77
78impl<T: LevelReader> SignalGetter for T {
79 fn is_redstone_conductor(&self, state: BlockStateId, pos: BlockPos) -> bool {
80 is_redstone_conductor(self, state, pos)
81 }
82
83 fn get_direct_signal(&self, pos: BlockPos, direction: Direction) -> i32 {
84 get_direct_signal(self, pos, direction, SignalQueryContext::DEFAULT)
85 }
86
87 fn get_direct_signal_to(&self, pos: BlockPos) -> i32 {
88 get_direct_signal_to(self, pos, SignalQueryContext::DEFAULT)
89 }
90
91 fn get_control_input_signal(
92 &self,
93 pos: BlockPos,
94 direction: Direction,
95 only_diodes: bool,
96 ) -> i32 {
97 get_control_input_signal(self, pos, direction, only_diodes)
98 }
99
100 fn has_signal(&self, pos: BlockPos, direction: Direction) -> bool {
101 get_signal(self, pos, direction, SignalQueryContext::DEFAULT) > 0
102 }
103
104 fn get_signal(&self, pos: BlockPos, direction: Direction) -> i32 {
105 get_signal(self, pos, direction, SignalQueryContext::DEFAULT)
106 }
107
108 fn get_best_own_or_neighbour_signal(&self, pos: BlockPos) -> i32 {
109 get_best_own_or_neighbour_signal(self, pos, SignalQueryContext::DEFAULT)
110 }
111
112 fn has_neighbor_signal(&self, pos: BlockPos) -> bool {
113 has_neighbor_signal(self, pos, SignalQueryContext::DEFAULT)
114 }
115
116 fn get_best_neighbor_signal(&self, pos: BlockPos) -> i32 {
117 get_best_neighbor_signal(self, pos, SignalQueryContext::DEFAULT)
118 }
119}
120
121pub(crate) fn is_redstone_conductor(
122 level: &dyn LevelReader,
123 state: BlockStateId,
124 pos: BlockPos,
125) -> bool {
126 BLOCK_BEHAVIORS
127 .get_behavior(state.get_block())
128 .is_redstone_conductor(state, level, pos)
129}
130
131pub(crate) fn get_direct_signal(
132 level: &dyn LevelReader,
133 pos: BlockPos,
134 direction: Direction,
135 context: SignalQueryContext,
136) -> i32 {
137 let state = level.get_block_state(pos);
138 BLOCK_BEHAVIORS
139 .get_behavior(state.get_block())
140 .get_direct_signal(state, level, pos, direction, context)
141}
142
143pub(crate) fn get_direct_signal_to(
144 level: &dyn LevelReader,
145 pos: BlockPos,
146 context: SignalQueryContext,
147) -> i32 {
148 let mut result = 0;
149 for direction in Direction::ALL {
150 result = result.max(get_direct_signal(
151 level,
152 direction.relative(pos),
153 direction,
154 context,
155 ));
156 if result >= 15 {
157 return result;
158 }
159 }
160 result
161}
162
163pub(crate) fn get_control_input_signal(
164 level: &dyn LevelReader,
165 pos: BlockPos,
166 direction: Direction,
167 only_diodes: bool,
168) -> i32 {
169 let state = level.get_block_state(pos);
170 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
171 if only_diodes {
172 return if behavior.is_diode() {
173 get_direct_signal(level, pos, direction, SignalQueryContext::DEFAULT)
174 } else {
175 0
176 };
177 }
178 if state.get_block() == &vanilla_blocks::REDSTONE_BLOCK {
179 return 15;
180 }
181 if state.get_block() == &vanilla_blocks::REDSTONE_WIRE {
182 return i32::from(state.get_value(&BlockStateProperties::POWER));
183 }
184 if behavior.is_signal_source(state, SignalQueryContext::DEFAULT) {
185 get_direct_signal(level, pos, direction, SignalQueryContext::DEFAULT)
186 } else {
187 0
188 }
189}
190
191pub(crate) fn get_signal(
192 level: &dyn LevelReader,
193 pos: BlockPos,
194 direction: Direction,
195 context: SignalQueryContext,
196) -> i32 {
197 let state = level.get_block_state(pos);
198 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
199 let signal = behavior.get_signal(state, level, pos, direction, context);
200 if behavior.is_redstone_conductor(state, level, pos) {
201 signal.max(get_direct_signal_to(level, pos, context))
202 } else {
203 signal
204 }
205}
206
207pub(crate) fn get_best_own_or_neighbour_signal(
208 level: &dyn LevelReader,
209 pos: BlockPos,
210 context: SignalQueryContext,
211) -> i32 {
212 let state = level.get_block_state(pos);
213 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
214 let own_signal = if behavior.is_signal_source(state, context) {
215 behavior.get_own_signal(state, level, pos, context)
216 } else {
217 0
218 };
219 get_best_neighbor_signal(level, pos, context).max(own_signal)
220}
221
222pub(crate) fn has_neighbor_signal(
223 level: &dyn LevelReader,
224 pos: BlockPos,
225 context: SignalQueryContext,
226) -> bool {
227 Direction::ALL
228 .into_iter()
229 .any(|direction| get_signal(level, direction.relative(pos), direction, context) > 0)
230}
231
232pub(crate) fn get_best_neighbor_signal(
233 level: &dyn LevelReader,
234 pos: BlockPos,
235 context: SignalQueryContext,
236) -> i32 {
237 let mut best = 0;
238 for direction in Direction::ALL {
239 let signal = get_signal(level, direction.relative(pos), direction, context);
240 if signal >= 15 {
241 return 15;
242 }
243 best = best.max(signal);
244 }
245 best
246}
247
248#[cfg(test)]
249mod tests {
250 use steel_registry::blocks::properties::{AttachFace, BlockStateProperties};
251 use steel_registry::{init_vanilla_registry, vanilla_blocks};
252
253 use super::*;
254 use crate::behavior::init_behaviors;
255
256 struct SignalTestLevel {
257 states: Vec<(BlockPos, BlockStateId)>,
258 }
259
260 impl SignalTestLevel {
261 fn new(states: Vec<(BlockPos, BlockStateId)>) -> Self {
262 Self { states }
263 }
264 }
265
266 impl LevelReader for SignalTestLevel {
267 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
268 self.states
269 .iter()
270 .find_map(|(state_pos, state)| (*state_pos == pos).then_some(*state))
271 .unwrap_or_else(|| vanilla_blocks::AIR.default_state())
272 }
273
274 fn raw_brightness(&self, _pos: BlockPos, _sky_darkening: u8) -> u8 {
275 0
276 }
277
278 fn min_y(&self) -> i32 {
279 -64
280 }
281
282 fn height(&self) -> i32 {
283 384
284 }
285 }
286
287 #[test]
288 fn powered_button_directly_powers_its_support_block() {
289 init_vanilla_registry();
290 init_behaviors();
291 let target = BlockPos::new(4, 64, -3);
292 let button = vanilla_blocks::STONE_BUTTON
293 .default_state()
294 .set_value(&BlockStateProperties::ATTACH_FACE, AttachFace::Ceiling)
295 .set_value(&BlockStateProperties::POWERED, true);
296 let level = SignalTestLevel::new(vec![
297 (target, vanilla_blocks::STONE.default_state()),
298 (target.below(), button),
299 ]);
300
301 assert_eq!(level.get_direct_signal_to(target), 15);
302 assert_eq!(level.get_signal(target, Direction::East), 15);
303 }
304
305 #[test]
306 fn non_conductor_does_not_relay_direct_signal() {
307 init_vanilla_registry();
308 init_behaviors();
309 let target = BlockPos::new(4, 64, -3);
310 let button = vanilla_blocks::STONE_BUTTON
311 .default_state()
312 .set_value(&BlockStateProperties::ATTACH_FACE, AttachFace::Ceiling)
313 .set_value(&BlockStateProperties::POWERED, true);
314 let level = SignalTestLevel::new(vec![
315 (target, vanilla_blocks::GLASS.default_state()),
316 (target.below(), button),
317 ]);
318
319 assert_eq!(level.get_direct_signal_to(target), 15);
320 assert_eq!(level.get_signal(target, Direction::East), 0);
321 }
322
323 #[test]
324 fn control_input_special_cases_redstone_block_and_wire() {
325 init_vanilla_registry();
326 init_behaviors();
327 let redstone_block_pos = BlockPos::new(0, 64, 0);
328 let wire_pos = redstone_block_pos.east();
329 let wire = vanilla_blocks::REDSTONE_WIRE
330 .default_state()
331 .set_value(&BlockStateProperties::POWER, 7_u8);
332 let level = SignalTestLevel::new(vec![
333 (
334 redstone_block_pos,
335 vanilla_blocks::REDSTONE_BLOCK.default_state(),
336 ),
337 (wire_pos, wire),
338 ]);
339
340 assert_eq!(
341 level.get_control_input_signal(redstone_block_pos, Direction::North, false),
342 15
343 );
344 assert_eq!(level.get_signal(redstone_block_pos, Direction::Down), 15);
345 assert_eq!(
346 level.get_best_own_or_neighbour_signal(redstone_block_pos),
347 15
348 );
349 assert_eq!(
350 level.get_control_input_signal(wire_pos, Direction::North, false),
351 7
352 );
353 assert_eq!(
354 level.get_control_input_signal(redstone_block_pos, Direction::North, true),
355 0
356 );
357 }
358}