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, EnumProperty, RedstoneSide};
9use steel_registry::{REGISTRY, vanilla_blocks};
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId, Direction};
12
13use super::evaluator::DefaultRedstoneWireEvaluator;
14use crate::behavior::{
15 BLOCK_BEHAVIORS, BlockBehavior, BlockHitResult, BlockPlaceContext, InteractionResult,
16 InventoryAccess,
17};
18use crate::player::Player;
19use crate::world::{
20 LevelReader, ScheduledTickAccess, SignalQueryContext, World, is_redstone_conductor,
21};
22
23#[block_behavior]
29pub struct RedStoneWireBlock {
30 block: BlockRef,
31 cross_state: BlockStateId,
32 evaluator: DefaultRedstoneWireEvaluator,
33}
34
35impl RedStoneWireBlock {
36 #[must_use]
38 pub fn new(block: BlockRef) -> Self {
39 let cross_state = block
40 .default_state()
41 .set_value(&BlockStateProperties::NORTH_REDSTONE, RedstoneSide::Side)
42 .set_value(&BlockStateProperties::EAST_REDSTONE, RedstoneSide::Side)
43 .set_value(&BlockStateProperties::SOUTH_REDSTONE, RedstoneSide::Side)
44 .set_value(&BlockStateProperties::WEST_REDSTONE, RedstoneSide::Side);
45 Self {
46 block,
47 cross_state,
48 evaluator: DefaultRedstoneWireEvaluator::new(block),
49 }
50 }
51
52 const fn property_for_direction(
53 direction: Direction,
54 ) -> Option<&'static EnumProperty<RedstoneSide>> {
55 match direction {
56 Direction::North => Some(&BlockStateProperties::NORTH_REDSTONE),
57 Direction::East => Some(&BlockStateProperties::EAST_REDSTONE),
58 Direction::South => Some(&BlockStateProperties::SOUTH_REDSTONE),
59 Direction::West => Some(&BlockStateProperties::WEST_REDSTONE),
60 Direction::Down | Direction::Up => None,
61 }
62 }
63
64 fn is_connected(side: RedstoneSide) -> bool {
65 side != RedstoneSide::None
66 }
67
68 fn is_cross(state: BlockStateId) -> bool {
69 Direction::HORIZONTAL.into_iter().all(|direction| {
70 Self::property_for_direction(direction)
71 .is_some_and(|property| Self::is_connected(state.get_value(property)))
72 })
73 }
74
75 fn is_dot(state: BlockStateId) -> bool {
76 Direction::HORIZONTAL.into_iter().all(|direction| {
77 Self::property_for_direction(direction)
78 .is_some_and(|property| !Self::is_connected(state.get_value(property)))
79 })
80 }
81
82 fn get_connection_state(
83 &self,
84 level: &dyn LevelReader,
85 state: BlockStateId,
86 pos: BlockPos,
87 ) -> BlockStateId {
88 let was_dot = Self::is_dot(state);
89 let mut state = self.get_missing_connections(
90 level,
91 self.block.default_state().set_value(
92 &BlockStateProperties::POWER,
93 state.get_value(&BlockStateProperties::POWER),
94 ),
95 pos,
96 );
97 if was_dot && Self::is_dot(state) {
98 return state;
99 }
100
101 let north = Self::is_connected(state.get_value(&BlockStateProperties::NORTH_REDSTONE));
102 let south = Self::is_connected(state.get_value(&BlockStateProperties::SOUTH_REDSTONE));
103 let east = Self::is_connected(state.get_value(&BlockStateProperties::EAST_REDSTONE));
104 let west = Self::is_connected(state.get_value(&BlockStateProperties::WEST_REDSTONE));
105 let north_south_empty = !north && !south;
106 let east_west_empty = !east && !west;
107
108 if !west && north_south_empty {
109 state = state.set_value(&BlockStateProperties::WEST_REDSTONE, RedstoneSide::Side);
110 }
111 if !east && north_south_empty {
112 state = state.set_value(&BlockStateProperties::EAST_REDSTONE, RedstoneSide::Side);
113 }
114 if !north && east_west_empty {
115 state = state.set_value(&BlockStateProperties::NORTH_REDSTONE, RedstoneSide::Side);
116 }
117 if !south && east_west_empty {
118 state = state.set_value(&BlockStateProperties::SOUTH_REDSTONE, RedstoneSide::Side);
119 }
120
121 state
122 }
123
124 fn get_missing_connections(
125 &self,
126 level: &dyn LevelReader,
127 mut state: BlockStateId,
128 pos: BlockPos,
129 ) -> BlockStateId {
130 let above_state = level.get_block_state(pos.above());
131 let can_connect_up = !is_redstone_conductor(level, above_state, pos);
134
135 for direction in Direction::HORIZONTAL {
136 let Some(property) = Self::property_for_direction(direction) else {
137 continue;
138 };
139 if !Self::is_connected(state.get_value(property)) {
140 state = state.set_value(
141 property,
142 self.get_connecting_side_with_up(level, pos, direction, can_connect_up),
143 );
144 }
145 }
146
147 state
148 }
149
150 fn get_connecting_side(
151 &self,
152 level: &dyn LevelReader,
153 pos: BlockPos,
154 direction: Direction,
155 ) -> RedstoneSide {
156 let above_pos = pos.above();
157 let can_connect_up = !is_redstone_conductor(level, level.get_block_state(above_pos), pos);
158 self.get_connecting_side_with_up(level, pos, direction, can_connect_up)
159 }
160
161 fn get_connecting_side_with_up(
162 &self,
163 level: &dyn LevelReader,
164 pos: BlockPos,
165 direction: Direction,
166 can_connect_up: bool,
167 ) -> RedstoneSide {
168 let relative_pos = pos.relative(direction);
169 let relative_state = level.get_block_state(relative_pos);
170
171 if can_connect_up {
172 let behavior = BLOCK_BEHAVIORS.get_behavior(relative_state.get_block());
173 let is_placeable_above =
174 behavior.is_trapdoor() || Self::can_survive_on(level, relative_pos, relative_state);
175 if is_placeable_above
176 && self.should_connect_to(level.get_block_state(relative_pos.above()), None)
177 {
178 if level.is_face_sturdy(relative_state, relative_pos, direction.opposite()) {
179 return RedstoneSide::Up;
180 }
181 return RedstoneSide::Side;
182 }
183 }
184
185 if !self.should_connect_to(relative_state, Some(direction))
186 && (is_redstone_conductor(level, relative_state, relative_pos)
187 || !self.should_connect_to(level.get_block_state(relative_pos.below()), None))
188 {
189 RedstoneSide::None
190 } else {
191 RedstoneSide::Side
192 }
193 }
194
195 fn should_connect_to(&self, state: BlockStateId, direction: Option<Direction>) -> bool {
196 if state.get_block() == self.block {
197 return true;
198 }
199 if state.get_block() == &vanilla_blocks::REPEATER {
200 let facing = state.get_value(&BlockStateProperties::HORIZONTAL_FACING);
201 return direction == Some(facing) || direction == Some(facing.opposite());
202 }
203 if state.get_block() == &vanilla_blocks::OBSERVER {
204 return direction == Some(state.get_value(&BlockStateProperties::FACING));
205 }
206
207 direction.is_some()
208 && BLOCK_BEHAVIORS
209 .get_behavior(state.get_block())
210 .is_signal_source(state, SignalQueryContext::DEFAULT)
211 }
212
213 fn can_survive_on(level: &dyn LevelReader, pos: BlockPos, state: BlockStateId) -> bool {
214 level.is_face_sturdy(state, pos, Direction::Up)
215 || state.get_block() == &vanilla_blocks::HOPPER
216 }
217
218 fn check_corner_change_at(&self, world: &Arc<World>, pos: BlockPos) {
219 if world.get_block_state(pos).get_block() != self.block {
220 return;
221 }
222
223 world.update_neighbors_at(pos, self.block);
224 for direction in Direction::ALL {
225 world.update_neighbors_at(pos.relative(direction), self.block);
226 }
227 }
228
229 fn update_neighbors_of_neighboring_wires(&self, world: &Arc<World>, pos: BlockPos) {
230 for direction in Direction::HORIZONTAL {
231 self.check_corner_change_at(world, pos.relative(direction));
232 }
233
234 for direction in Direction::HORIZONTAL {
235 let target = pos.relative(direction);
236 let target_state = world.get_block_state(target);
237 if is_redstone_conductor(world.as_ref(), target_state, target) {
238 self.check_corner_change_at(world, target.above());
239 } else {
240 self.check_corner_change_at(world, target.below());
241 }
242 }
243 }
244
245 fn updates_on_shape_change(
246 world: &Arc<World>,
247 pos: BlockPos,
248 old_state: BlockStateId,
249 new_state: BlockStateId,
250 ) {
251 for direction in Direction::HORIZONTAL {
252 let Some(property) = Self::property_for_direction(direction) else {
253 continue;
254 };
255 if Self::is_connected(old_state.get_value(property))
256 == Self::is_connected(new_state.get_value(property))
257 {
258 continue;
259 }
260
261 let relative_pos = pos.relative(direction);
262 let relative_state = world.get_block_state(relative_pos);
263 if is_redstone_conductor(world.as_ref(), relative_state, relative_pos) {
264 world.update_neighbors_at_except_from_facing(
265 relative_pos,
266 new_state.get_block(),
267 direction.opposite(),
268 );
269 }
270 }
271 }
272}
273
274impl BlockBehavior for RedStoneWireBlock {
275 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
276 Some(self.get_connection_state(
277 context.world.as_ref(),
278 self.cross_state,
279 context.place_pos(),
280 ))
281 }
282
283 fn update_shape(
284 &self,
285 state: BlockStateId,
286 world: &dyn ScheduledTickAccess,
287 pos: BlockPos,
288 direction: Direction,
289 neighbor_pos: BlockPos,
290 neighbor_state: BlockStateId,
291 ) -> BlockStateId {
292 if direction == Direction::Down {
293 return if Self::can_survive_on(world, neighbor_pos, neighbor_state) {
294 state
295 } else {
296 REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR)
297 };
298 }
299 if direction == Direction::Up {
300 return self.get_connection_state(world, state, pos);
301 }
302
303 let Some(property) = Self::property_for_direction(direction) else {
304 return state;
305 };
306 let side_connection = self.get_connecting_side(world, pos, direction);
307 if Self::is_connected(side_connection) == Self::is_connected(state.get_value(property))
308 && !Self::is_cross(state)
309 {
310 state.set_value(property, side_connection)
311 } else {
312 self.get_connection_state(
313 world,
314 self.cross_state
315 .set_value(
316 &BlockStateProperties::POWER,
317 state.get_value(&BlockStateProperties::POWER),
318 )
319 .set_value(property, side_connection),
320 pos,
321 )
322 }
323 }
324
325 fn update_indirect_neighbour_shapes(
326 &self,
327 state: BlockStateId,
328 world: &Arc<World>,
329 pos: BlockPos,
330 flags: UpdateFlags,
331 update_limit: i32,
332 ) {
333 for direction in Direction::HORIZONTAL {
334 let Some(property) = Self::property_for_direction(direction) else {
335 continue;
336 };
337 if !Self::is_connected(state.get_value(property)) {
338 continue;
339 }
340
341 let adjacent_pos = pos.relative(direction);
342 if world.get_block_state(adjacent_pos).get_block() == self.block {
343 continue;
344 }
345
346 let below_pos = adjacent_pos.below();
347 if world.get_block_state(below_pos).get_block() == self.block {
348 let neighbor_pos = below_pos.relative(direction.opposite());
349 world.neighbor_shape_changed(
350 direction.opposite(),
351 below_pos,
352 neighbor_pos,
353 world.get_block_state(neighbor_pos),
354 flags,
355 update_limit,
356 );
357 }
358
359 let above_pos = adjacent_pos.above();
360 if world.get_block_state(above_pos).get_block() == self.block {
361 let neighbor_pos = above_pos.relative(direction.opposite());
362 world.neighbor_shape_changed(
363 direction.opposite(),
364 above_pos,
365 neighbor_pos,
366 world.get_block_state(neighbor_pos),
367 flags,
368 update_limit,
369 );
370 }
371 }
372 }
373
374 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
375 let below_pos = pos.below();
376 Self::can_survive_on(world, below_pos, world.get_block_state(below_pos))
377 }
378
379 fn on_place(
380 &self,
381 state: BlockStateId,
382 world: &Arc<World>,
383 pos: BlockPos,
384 old_state: BlockStateId,
385 _moved_by_piston: bool,
386 ) {
387 if old_state.get_block() == self.block {
388 return;
389 }
390
391 self.evaluator.update_power_strength(world, pos, state);
392 for direction in [Direction::Down, Direction::Up] {
393 world.update_neighbors_at(pos.relative(direction), self.block);
394 }
395 self.update_neighbors_of_neighboring_wires(world, pos);
396 }
397
398 fn affect_neighbors_after_removal(
399 &self,
400 state: BlockStateId,
401 world: &Arc<World>,
402 pos: BlockPos,
403 moved_by_piston: bool,
404 ) {
405 if moved_by_piston {
406 return;
407 }
408
409 for direction in Direction::ALL {
410 world.update_neighbors_at(pos.relative(direction), self.block);
411 }
412 self.evaluator.update_power_strength(world, pos, state);
413 self.update_neighbors_of_neighboring_wires(world, pos);
414 }
415
416 fn handle_neighbor_changed(
417 &self,
418 state: BlockStateId,
419 world: &Arc<World>,
420 pos: BlockPos,
421 _source_block: BlockRef,
422 _moved_by_piston: bool,
423 ) {
424 if self.can_survive(state, world.as_ref(), pos) {
425 self.evaluator.update_power_strength(world, pos, state);
426 } else {
427 world.drop_resources(state, pos);
428 world.remove_block(pos, false);
429 }
430 }
431
432 fn use_without_item(
433 &self,
434 state: BlockStateId,
435 world: &Arc<World>,
436 pos: BlockPos,
437 player: &Player,
438 _hit_result: &BlockHitResult,
439 _inv: &mut InventoryAccess,
440 ) -> InteractionResult {
441 if !player.abilities.lock().may_build {
442 return InteractionResult::Pass;
443 }
444 if !Self::is_cross(state) && !Self::is_dot(state) {
445 return InteractionResult::Pass;
446 }
447
448 let new_base_state = if Self::is_cross(state) {
449 self.block.default_state()
450 } else {
451 self.cross_state
452 };
453 let new_state = self.get_connection_state(
454 world.as_ref(),
455 new_base_state.set_value(
456 &BlockStateProperties::POWER,
457 state.get_value(&BlockStateProperties::POWER),
458 ),
459 pos,
460 );
461 if new_state == state {
462 return InteractionResult::Pass;
463 }
464
465 world.set_block(pos, new_state, UpdateFlags::UPDATE_ALL);
466 Self::updates_on_shape_change(world, pos, state, new_state);
467 InteractionResult::Success
468 }
469
470 fn is_signal_source(&self, _state: BlockStateId, context: SignalQueryContext) -> bool {
471 context.wire_signals_enabled()
472 }
473
474 fn get_own_signal(
475 &self,
476 state: BlockStateId,
477 _world: &dyn LevelReader,
478 _pos: BlockPos,
479 _context: SignalQueryContext,
480 ) -> i32 {
481 i32::from(state.get_value(&BlockStateProperties::POWER))
482 }
483
484 fn get_signal(
485 &self,
486 state: BlockStateId,
487 world: &dyn LevelReader,
488 pos: BlockPos,
489 direction: Direction,
490 context: SignalQueryContext,
491 ) -> i32 {
492 if !context.wire_signals_enabled() || direction == Direction::Down {
493 return 0;
494 }
495
496 let power = self.get_own_signal(state, world, pos, context);
497 if power == 0 {
498 return 0;
499 }
500 if direction == Direction::Up {
501 return power;
502 }
503
504 let Some(property) = Self::property_for_direction(direction.opposite()) else {
505 return 0;
506 };
507 if Self::is_connected(
508 self.get_connection_state(world, state, pos)
509 .get_value(property),
510 ) {
511 power
512 } else {
513 0
514 }
515 }
516
517 fn get_direct_signal(
518 &self,
519 state: BlockStateId,
520 world: &dyn LevelReader,
521 pos: BlockPos,
522 direction: Direction,
523 context: SignalQueryContext,
524 ) -> i32 {
525 if context.wire_signals_enabled() {
526 self.get_signal(state, world, pos, direction, context)
527 } else {
528 0
529 }
530 }
531
532 }
535
536#[cfg(test)]
537mod tests {
538 use steel_registry::init_vanilla_registry;
539
540 use super::*;
541 use crate::behavior::init_behaviors;
542 use crate::test_support::TestLevel;
543
544 fn wire() -> RedStoneWireBlock {
545 init_vanilla_registry();
546 init_behaviors();
547 RedStoneWireBlock::new(&vanilla_blocks::REDSTONE_WIRE)
548 }
549
550 #[test]
551 fn isolated_cross_and_dot_preserve_their_vanilla_shapes() {
552 let behavior = wire();
553 let level = TestLevel::default().with_block(
554 BlockPos::new(0, 63, 0),
555 vanilla_blocks::STONE.default_state(),
556 );
557 let pos = BlockPos::new(0, 64, 0);
558
559 let cross = behavior.get_connection_state(&level, behavior.cross_state, pos);
560 let dot = behavior.get_connection_state(
561 &level,
562 vanilla_blocks::REDSTONE_WIRE.default_state(),
563 pos,
564 );
565
566 assert!(RedStoneWireBlock::is_cross(cross));
567 assert!(RedStoneWireBlock::is_dot(dot));
568 }
569
570 #[test]
571 fn wire_climbs_sturdy_neighbor_only_when_above_is_connectable() {
572 let behavior = wire();
573 let pos = BlockPos::new(0, 64, 0);
574 let level = TestLevel::default()
575 .with_block(pos.below(), vanilla_blocks::STONE.default_state())
576 .with_block(pos.east(), vanilla_blocks::STONE.default_state())
577 .with_block(pos.east().above(), behavior.block.default_state());
578
579 assert_eq!(
580 behavior.get_connecting_side(&level, pos, Direction::East),
581 RedstoneSide::Up
582 );
583
584 level.set_test_block(pos.above(), vanilla_blocks::STONE.default_state());
585 assert_eq!(
586 behavior.get_connecting_side(&level, pos, Direction::East),
587 RedstoneSide::None
588 );
589 }
590
591 #[test]
592 fn powered_wire_signal_follows_recomputed_connections() {
593 let behavior = wire();
594 let pos = BlockPos::new(0, 64, 0);
595 let state = behavior
596 .block
597 .default_state()
598 .set_value(&BlockStateProperties::POWER, 9)
599 .set_value(&BlockStateProperties::NORTH_REDSTONE, RedstoneSide::Side)
600 .set_value(&BlockStateProperties::EAST_REDSTONE, RedstoneSide::Side);
601 let level = TestLevel::default()
602 .with_block(pos.below(), vanilla_blocks::STONE.default_state())
603 .with_block(pos.north(), behavior.block.default_state())
604 .with_block(pos.east(), behavior.block.default_state());
605
606 assert_eq!(
607 behavior.get_signal(
608 state,
609 &level,
610 pos,
611 Direction::West,
612 SignalQueryContext::DEFAULT,
613 ),
614 9
615 );
616 assert_eq!(
617 behavior.get_signal(
618 state,
619 &level,
620 pos,
621 Direction::East,
622 SignalQueryContext::DEFAULT,
623 ),
624 0
625 );
626 assert_eq!(
627 behavior.get_signal(
628 state,
629 &level,
630 pos,
631 Direction::Up,
632 SignalQueryContext::without_wire_signals(),
633 ),
634 0
635 );
636 }
637}