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