1use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::blocks::properties::{BlockStateProperties, Direction};
9use steel_registry::{REGISTRY, vanilla_blocks};
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId};
12
13use super::base::DiodeBlock;
14use crate::behavior::{
15 BlockBehavior, BlockHitResult, BlockPlaceContext, InteractionResult, InventoryAccess,
16 PlacementSource,
17};
18use crate::player::Player;
19use crate::world::{LevelReader, ScheduledTickAccess, SignalQueryContext, World};
20
21#[block_behavior]
23pub struct RepeaterBlock {
24 diode: DiodeBlock,
25}
26
27impl RepeaterBlock {
28 #[must_use]
30 pub const fn new(block: BlockRef) -> Self {
31 Self {
32 diode: DiodeBlock::new(block),
33 }
34 }
35
36 fn delay(state: BlockStateId) -> i32 {
37 i32::from(state.get_value(&BlockStateProperties::DELAY)) * 2
38 }
39
40 fn is_locked_at(level: &dyn LevelReader, pos: BlockPos, state: BlockStateId) -> bool {
41 DiodeBlock::get_alternate_signal(level, pos, state, true) > 0
42 }
43
44 fn should_turn_on(level: &dyn LevelReader, pos: BlockPos, state: BlockStateId) -> bool {
45 DiodeBlock::get_input_signal(level, pos, state) > 0
46 }
47
48 fn check_tick_on_neighbor(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
49 self.diode.check_tick_on_neighbor(
50 world,
51 pos,
52 state,
53 Self::is_locked_at(world.as_ref(), pos, state),
54 Self::should_turn_on(world.as_ref(), pos, state),
55 Self::delay(state),
56 );
57 }
58}
59
60impl BlockBehavior for RepeaterBlock {
61 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
62 DiodeBlock::can_survive(world, pos)
63 }
64
65 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
66 let state = self.diode.state_for_placement(context);
67 Some(state.set_value(
68 &BlockStateProperties::LOCKED,
69 Self::is_locked_at(context.world.as_ref(), context.place_pos(), state),
70 ))
71 }
72
73 fn update_shape(
74 &self,
75 state: BlockStateId,
76 world: &dyn ScheduledTickAccess,
77 pos: BlockPos,
78 direction: Direction,
79 neighbor_pos: BlockPos,
80 neighbor_state: BlockStateId,
81 ) -> BlockStateId {
82 if direction == Direction::Down
83 && !DiodeBlock::can_survive_on(world, neighbor_pos, neighbor_state)
84 {
85 return REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR);
86 }
87
88 let facing = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
89 if direction.get_axis() == facing.get_axis() {
90 state
91 } else {
92 state.set_value(
93 &BlockStateProperties::LOCKED,
94 Self::is_locked_at(world, pos, state),
95 )
96 }
97 }
98
99 fn use_without_item(
100 &self,
101 state: BlockStateId,
102 world: &Arc<World>,
103 pos: BlockPos,
104 player: &Player,
105 _hit_result: &BlockHitResult,
106 _inv: &mut InventoryAccess,
107 ) -> InteractionResult {
108 if !player.abilities.lock().may_build {
109 return InteractionResult::Pass;
110 }
111
112 let delay = state.get_value(&BlockStateProperties::DELAY);
113 let next_delay = if delay == 4 { 1 } else { delay + 1 };
114 world.set_block(
115 pos,
116 state.set_value(&BlockStateProperties::DELAY, next_delay),
117 UpdateFlags::UPDATE_ALL,
118 );
119 InteractionResult::Success
120 }
121
122 fn handle_neighbor_changed(
123 &self,
124 state: BlockStateId,
125 world: &Arc<World>,
126 pos: BlockPos,
127 _source_block: BlockRef,
128 _moved_by_piston: bool,
129 ) {
130 self.diode.handle_neighbor_changed(state, world, pos, || {
131 self.check_tick_on_neighbor(state, world, pos);
132 });
133 }
134
135 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
136 self.diode.tick(
137 state,
138 world,
139 pos,
140 Self::is_locked_at(world.as_ref(), pos, state),
141 Self::should_turn_on(world.as_ref(), pos, state),
142 Self::delay(state),
143 );
144 }
145
146 fn set_placed_by(
147 &self,
148 state: BlockStateId,
149 world: &Arc<World>,
150 pos: BlockPos,
151 _source: &PlacementSource<'_>,
152 ) {
153 self.diode
154 .set_placed_by(world, pos, Self::should_turn_on(world.as_ref(), pos, state));
155 }
156
157 fn on_place(
158 &self,
159 state: BlockStateId,
160 world: &Arc<World>,
161 pos: BlockPos,
162 _old_state: BlockStateId,
163 _moved_by_piston: bool,
164 ) {
165 self.diode.on_place(state, world, pos);
166 }
167
168 fn affect_neighbors_after_removal(
169 &self,
170 state: BlockStateId,
171 world: &Arc<World>,
172 pos: BlockPos,
173 moved_by_piston: bool,
174 ) {
175 self.diode
176 .affect_neighbors_after_removal(state, world, pos, moved_by_piston);
177 }
178
179 fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
180 true
181 }
182
183 fn is_diode(&self) -> bool {
184 true
185 }
186
187 fn get_own_signal(
188 &self,
189 state: BlockStateId,
190 _world: &dyn LevelReader,
191 _pos: BlockPos,
192 _context: SignalQueryContext,
193 ) -> i32 {
194 DiodeBlock::own_signal(state, 15)
195 }
196
197 fn get_signal(
198 &self,
199 state: BlockStateId,
200 _world: &dyn LevelReader,
201 _pos: BlockPos,
202 direction: Direction,
203 _context: SignalQueryContext,
204 ) -> i32 {
205 DiodeBlock::signal(state, direction, 15)
206 }
207
208 fn get_direct_signal(
209 &self,
210 state: BlockStateId,
211 world: &dyn LevelReader,
212 pos: BlockPos,
213 direction: Direction,
214 context: SignalQueryContext,
215 ) -> i32 {
216 self.get_signal(state, world, pos, direction, context)
217 }
218
219 }
221
222#[cfg(test)]
223mod tests {
224 use steel_registry::init_vanilla_registry;
225
226 use super::*;
227 use crate::behavior::init_behaviors;
228 use crate::test_support::TestLevel;
229 use crate::world::tick_scheduler::TickPriority;
230
231 fn repeater() -> RepeaterBlock {
232 init_vanilla_registry();
233 init_behaviors();
234 RepeaterBlock::new(&vanilla_blocks::REPEATER)
235 }
236
237 fn repeater_state(facing: Direction, powered: bool) -> BlockStateId {
238 vanilla_blocks::REPEATER
239 .default_state()
240 .set_value(&BlockStateProperties::HORIZONTAL_FACING, facing)
241 .set_value(&BlockStateProperties::POWERED, powered)
242 }
243
244 #[test]
245 fn input_reads_wire_power_in_front() {
246 let _repeater = repeater();
247 let pos = BlockPos::new(0, 64, 0);
248 let state = repeater_state(Direction::East, false);
249 let wire = vanilla_blocks::REDSTONE_WIRE
250 .default_state()
251 .set_value(&BlockStateProperties::POWER, 7);
252 let level = TestLevel::default().with_block(pos.east(), wire);
253
254 assert_eq!(DiodeBlock::get_input_signal(&level, pos, state), 7);
255 }
256
257 #[test]
258 fn side_lock_accepts_only_another_diode() {
259 let _repeater = repeater();
260 let pos = BlockPos::new(0, 64, 0);
261 let state = repeater_state(Direction::North, false);
262 let side_repeater = repeater_state(Direction::East, true);
263 let level = TestLevel::default().with_block(pos.east(), side_repeater);
264 assert!(RepeaterBlock::is_locked_at(&level, pos, state));
265
266 let redstone_block_level = TestLevel::default()
267 .with_block(pos.east(), vanilla_blocks::REDSTONE_BLOCK.default_state());
268 assert!(!RepeaterBlock::is_locked_at(
269 &redstone_block_level,
270 pos,
271 state
272 ));
273 }
274
275 #[test]
276 fn powered_output_is_directional() {
277 let repeater = repeater();
278 let state = repeater_state(Direction::West, true);
279 let level = TestLevel::default();
280 let pos = BlockPos::new(0, 64, 0);
281
282 assert_eq!(
283 repeater.get_signal(
284 state,
285 &level,
286 pos,
287 Direction::West,
288 SignalQueryContext::DEFAULT,
289 ),
290 15
291 );
292 assert_eq!(
293 repeater.get_signal(
294 state,
295 &level,
296 pos,
297 Direction::East,
298 SignalQueryContext::DEFAULT,
299 ),
300 0
301 );
302 }
303
304 #[test]
305 fn support_requires_vanilla_rigid_face() {
306 let repeater = repeater();
307 let pos = BlockPos::new(0, 64, 0);
308 let stone =
309 TestLevel::default().with_block(pos.below(), vanilla_blocks::STONE.default_state());
310 let air = TestLevel::default();
311
312 assert!(repeater.can_survive(vanilla_blocks::REPEATER.default_state(), &stone, pos));
313 assert!(!repeater.can_survive(vanilla_blocks::REPEATER.default_state(), &air, pos));
314 }
315
316 #[test]
317 fn neighbor_tick_priority_matches_vanilla_diode_ordering() {
318 let _repeater = repeater();
319 let pos = BlockPos::new(0, 64, 0);
320 let off = repeater_state(Direction::North, false);
321 let on = repeater_state(Direction::North, true);
322 let plain_level = TestLevel::default();
323
324 assert_eq!(
325 DiodeBlock::tick_priority(&plain_level, pos, off, false),
326 TickPriority::High
327 );
328 assert_eq!(
329 DiodeBlock::tick_priority(&plain_level, pos, on, true),
330 TickPriority::VeryHigh
331 );
332
333 let crossing_diode = repeater_state(Direction::East, false);
334 let prioritized_level = TestLevel::default().with_block(pos.south(), crossing_diode);
335 assert_eq!(
336 DiodeBlock::tick_priority(&prioritized_level, pos, off, false),
337 TickPriority::ExtremelyHigh
338 );
339 }
340}