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 use steel_worldgen::density_functions::overworld::OverworldNoiseSettings;
254
255 use super::*;
256 use crate::behavior::init_behaviors;
257
258 struct SignalTestLevel {
259 states: Vec<(BlockPos, BlockStateId)>,
260 }
261
262 impl SignalTestLevel {
263 fn new(states: Vec<(BlockPos, BlockStateId)>) -> Self {
264 Self { states }
265 }
266 }
267
268 impl LevelReader for SignalTestLevel {
269 fn get_block_state(&self, pos: BlockPos) -> BlockStateId {
270 self.states
271 .iter()
272 .find_map(|(state_pos, state)| (*state_pos == pos).then_some(*state))
273 .unwrap_or_else(|| vanilla_blocks::AIR.default_state())
274 }
275
276 fn raw_brightness(&self, _pos: BlockPos, _sky_darkening: u8) -> u8 {
277 0
278 }
279
280 fn min_y(&self) -> i32 {
281 OverworldNoiseSettings::MIN_Y
282 }
283
284 fn height(&self) -> i32 {
285 OverworldNoiseSettings::HEIGHT
286 }
287
288 fn sea_level(&self) -> i32 {
289 OverworldNoiseSettings::SEA_LEVEL
290 }
291 }
292
293 #[test]
294 fn powered_button_directly_powers_its_support_block() {
295 init_vanilla_registry();
296 init_behaviors();
297 let target = BlockPos::new(4, 64, -3);
298 let button = vanilla_blocks::STONE_BUTTON
299 .default_state()
300 .set_value(&BlockStateProperties::ATTACH_FACE, AttachFace::Ceiling)
301 .set_value(&BlockStateProperties::POWERED, true);
302 let level = SignalTestLevel::new(vec![
303 (target, vanilla_blocks::STONE.default_state()),
304 (target.below(), button),
305 ]);
306
307 assert_eq!(level.get_direct_signal_to(target), 15);
308 assert_eq!(level.get_signal(target, Direction::East), 15);
309 }
310
311 #[test]
312 fn non_conductor_does_not_relay_direct_signal() {
313 init_vanilla_registry();
314 init_behaviors();
315 let target = BlockPos::new(4, 64, -3);
316 let button = vanilla_blocks::STONE_BUTTON
317 .default_state()
318 .set_value(&BlockStateProperties::ATTACH_FACE, AttachFace::Ceiling)
319 .set_value(&BlockStateProperties::POWERED, true);
320 let level = SignalTestLevel::new(vec![
321 (target, vanilla_blocks::GLASS.default_state()),
322 (target.below(), button),
323 ]);
324
325 assert_eq!(level.get_direct_signal_to(target), 15);
326 assert_eq!(level.get_signal(target, Direction::East), 0);
327 }
328
329 #[test]
330 fn control_input_special_cases_redstone_block_and_wire() {
331 init_vanilla_registry();
332 init_behaviors();
333 let redstone_block_pos = BlockPos::new(0, 64, 0);
334 let wire_pos = redstone_block_pos.east();
335 let wire = vanilla_blocks::REDSTONE_WIRE
336 .default_state()
337 .set_value(&BlockStateProperties::POWER, 7_u8);
338 let level = SignalTestLevel::new(vec![
339 (
340 redstone_block_pos,
341 vanilla_blocks::REDSTONE_BLOCK.default_state(),
342 ),
343 (wire_pos, wire),
344 ]);
345
346 assert_eq!(
347 level.get_control_input_signal(redstone_block_pos, Direction::North, false),
348 15
349 );
350 assert_eq!(level.get_signal(redstone_block_pos, Direction::Down), 15);
351 assert_eq!(
352 level.get_best_own_or_neighbour_signal(redstone_block_pos),
353 15
354 );
355 assert_eq!(
356 level.get_control_input_signal(wire_pos, Direction::North, false),
357 7
358 );
359 assert_eq!(
360 level.get_control_input_signal(redstone_block_pos, Direction::North, true),
361 0
362 );
363 }
364}