steel_core/behavior/blocks/redstone/wire/
evaluator.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;
8use steel_utils::types::UpdateFlags;
9use steel_utils::{BlockPos, BlockStateId, Direction};
10
11use crate::world::{
12 LevelReader, SignalQueryContext, World, get_best_neighbor_signal, is_redstone_conductor,
13};
14
15use crate::behavior::blocks::redstone::java_hash::sort_small_map_positions;
16
17pub(super) struct DefaultRedstoneWireEvaluator {
19 wire_block: BlockRef,
20}
21
22impl DefaultRedstoneWireEvaluator {
23 pub(super) const fn new(wire_block: BlockRef) -> Self {
24 Self { wire_block }
25 }
26
27 pub(super) fn update_power_strength(
28 &self,
29 world: &Arc<World>,
30 pos: BlockPos,
31 state: BlockStateId,
32 ) {
33 let target_strength = self.calculate_target_strength(world.as_ref(), pos);
34 if i32::from(state.get_value(&BlockStateProperties::POWER)) == target_strength {
35 return;
36 }
37
38 if world.get_block_state(pos) == state {
39 world.set_block(
40 pos,
41 state.set_value(&BlockStateProperties::POWER, target_strength as u8),
42 UpdateFlags::UPDATE_CLIENTS,
43 );
44 }
45
46 for update_pos in java_hash_set_update_order(pos) {
47 world.update_neighbors_at(update_pos, self.wire_block);
48 }
49 }
50
51 fn calculate_target_strength(&self, level: &dyn LevelReader, pos: BlockPos) -> i32 {
52 let block_signal =
53 get_best_neighbor_signal(level, pos, SignalQueryContext::without_wire_signals());
54 if block_signal == 15 {
55 return block_signal;
56 }
57 block_signal.max(self.get_incoming_wire_signal(level, pos))
58 }
59
60 fn get_incoming_wire_signal(&self, level: &dyn LevelReader, pos: BlockPos) -> i32 {
61 let mut wire_signal = 0;
62
63 for direction in Direction::HORIZONTAL {
64 let neighbor_pos = pos.relative(direction);
65 let neighbor_state = level.get_block_state(neighbor_pos);
66 wire_signal = wire_signal.max(self.get_wire_signal(neighbor_state));
67
68 let above_pos = pos.above();
69 if is_redstone_conductor(level, neighbor_state, neighbor_pos)
70 && !is_redstone_conductor(level, level.get_block_state(above_pos), above_pos)
71 {
72 let above_neighbor_pos = neighbor_pos.above();
73 wire_signal = wire_signal
74 .max(self.get_wire_signal(level.get_block_state(above_neighbor_pos)));
75 } else if !is_redstone_conductor(level, neighbor_state, neighbor_pos) {
76 let below_neighbor_pos = neighbor_pos.below();
77 wire_signal = wire_signal
78 .max(self.get_wire_signal(level.get_block_state(below_neighbor_pos)));
79 }
80 }
81
82 0.max(wire_signal - 1)
83 }
84
85 fn get_wire_signal(&self, state: BlockStateId) -> i32 {
86 if state.get_block() == self.wire_block {
87 i32::from(state.get_value(&BlockStateProperties::POWER))
88 } else {
89 0
90 }
91 }
92}
93
94fn java_hash_set_update_order(pos: BlockPos) -> [BlockPos; 7] {
102 let mut positions = [
103 pos,
104 pos.below(),
105 pos.above(),
106 pos.north(),
107 pos.south(),
108 pos.west(),
109 pos.east(),
110 ];
111
112 sort_small_map_positions(&mut positions);
113
114 positions
115}
116
117#[cfg(test)]
118mod tests {
119 use steel_registry::init_vanilla_registry;
120 use steel_registry::vanilla_blocks;
121
122 use super::*;
123 use crate::behavior::init_behaviors;
124 use crate::test_support::TestLevel;
125
126 fn expected_positions(pos: BlockPos, labels: [&str; 7]) -> [BlockPos; 7] {
127 labels.map(|label| match label {
128 "center" => pos,
129 "down" => pos.below(),
130 "up" => pos.above(),
131 "north" => pos.north(),
132 "south" => pos.south(),
133 "west" => pos.west(),
134 "east" => pos.east(),
135 _ => panic!("invalid test direction label"),
136 })
137 }
138
139 #[test]
140 fn seven_position_order_matches_target_jdk_hash_set_fixtures() {
141 let fixtures = [
142 (
143 BlockPos::new(0, 64, 0),
144 ["center", "down", "south", "east", "up", "north", "west"],
145 ),
146 (
147 BlockPos::new(1, 64, 0),
148 ["up", "north", "west", "center", "down", "south", "east"],
149 ),
150 (
151 BlockPos::new(15, 64, 0),
152 ["down", "south", "east", "up", "north", "west", "center"],
153 ),
154 (
155 BlockPos::new(16, 64, 0),
156 ["center", "down", "south", "east", "up", "north", "west"],
157 ),
158 (
159 BlockPos::new(-16, -64, 31),
160 ["down", "south", "east", "up", "north", "west", "center"],
161 ),
162 (
163 BlockPos::new(30_000_000, 319, -30_000_000),
164 ["down", "south", "east", "center", "up", "north", "west"],
165 ),
166 ];
167
168 for (pos, labels) in fixtures {
169 assert_eq!(
170 java_hash_set_update_order(pos),
171 expected_positions(pos, labels)
172 );
173 }
174 }
175
176 #[test]
177 fn incoming_wire_power_does_not_feed_back_through_signal_queries() {
178 init_vanilla_registry();
179 init_behaviors();
180 let pos = BlockPos::new(0, 64, 0);
181 let powered_neighbor = vanilla_blocks::REDSTONE_WIRE
182 .default_state()
183 .set_value(&BlockStateProperties::POWER, 15);
184 let level = TestLevel::default().with_block(pos.east(), powered_neighbor);
185 let evaluator = DefaultRedstoneWireEvaluator::new(&vanilla_blocks::REDSTONE_WIRE);
186
187 assert_eq!(evaluator.calculate_target_strength(&level, pos), 14);
188 }
189}