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