steel_core/behavior/blocks/redstone/rail/
rail_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::BlockRef;
5use steel_registry::blocks::block_state_ext::BlockStateExt as _;
6use steel_utils::{BlockPos, BlockStateId, Direction};
7
8use crate::behavior::{BLOCK_BEHAVIORS, BlockBehavior, BlockPlaceContext, RailBehavior};
9use crate::world::{LevelReader, ScheduledTickAccess, SignalQueryContext, World};
10
11use super::base_rail_block::BaseRailBlock;
12use super::rail_state::RailState;
13
14#[block_behavior]
16pub struct RailBlock {
17 base: BaseRailBlock,
18}
19
20impl RailBlock {
21 #[must_use]
23 pub const fn new(block: BlockRef) -> Self {
24 Self {
25 base: BaseRailBlock::new(block, false),
26 }
27 }
28}
29
30impl RailBehavior for RailBlock {
31 fn is_straight(&self) -> bool {
32 self.base.is_straight()
33 }
34}
35
36impl BlockBehavior for RailBlock {
37 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
38 Some(self.base.state_for_placement(context))
39 }
40
41 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
42 BaseRailBlock::can_survive(world, pos)
43 }
44
45 fn update_shape(
46 &self,
47 state: BlockStateId,
48 world: &dyn ScheduledTickAccess,
49 pos: BlockPos,
50 _direction: Direction,
51 _neighbor_pos: BlockPos,
52 _neighbor_state: BlockStateId,
53 ) -> BlockStateId {
54 BaseRailBlock::update_shape(state, world, pos)
55 }
56
57 fn on_place(
58 &self,
59 state: BlockStateId,
60 world: &Arc<World>,
61 pos: BlockPos,
62 old_state: BlockStateId,
63 moved_by_piston: bool,
64 ) {
65 if old_state.get_block() != self.base.block {
66 let _ = self
67 .base
68 .update_state_on_place(state, world, pos, moved_by_piston);
69 }
70 }
71
72 fn handle_neighbor_changed(
73 &self,
74 state: BlockStateId,
75 world: &Arc<World>,
76 pos: BlockPos,
77 source_block: BlockRef,
78 moved_by_piston: bool,
79 ) {
80 if !self
81 .base
82 .handle_neighbor_changed(state, world, pos, moved_by_piston)
83 {
84 return;
85 }
86
87 let source_behavior = BLOCK_BEHAVIORS.get_behavior(source_block);
88 if !source_behavior
89 .is_signal_source(source_block.default_state(), SignalQueryContext::DEFAULT)
90 {
91 return;
92 }
93 let Some(rail) = RailState::new(world, pos, state) else {
94 return;
95 };
96 if rail.count_potential_connections() == 3 {
97 let _ = BaseRailBlock::update_dir(world, pos, state, false);
98 }
99 }
100
101 fn affect_neighbors_after_removal(
102 &self,
103 state: BlockStateId,
104 world: &Arc<World>,
105 pos: BlockPos,
106 moved_by_piston: bool,
107 ) {
108 self.base
109 .affect_neighbors_after_removal(state, world, pos, moved_by_piston);
110 }
111
112 fn as_rail(&self) -> Option<&dyn RailBehavior> {
113 Some(self)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use steel_registry::blocks::properties::{BlockStateProperties, EnumProperty, RailShape};
120 use steel_registry::init_vanilla_registry;
121 use steel_registry::vanilla_blocks;
122 use steel_utils::ChunkPos;
123 use steel_utils::types::UpdateFlags;
124
125 use super::*;
126 use crate::behavior::{BLOCK_BEHAVIORS, init_behaviors};
127 use crate::test_support::{fresh_test_world, insert_ready_full_chunk};
128
129 const RAIL_SHAPE: &EnumProperty<RailShape> = &BlockStateProperties::RAIL_SHAPE;
130
131 #[test]
132 fn powered_three_way_junction_uses_vanilla_curve_priority() {
133 init_vanilla_registry();
134 init_behaviors();
135 let world_fixture = fresh_test_world("rail_three_way_switch");
136 let world = &world_fixture.world;
137 let center = BlockPos::new(8, 64, 8);
138 insert_ready_full_chunk(world, ChunkPos::from_block_pos(center));
139 let raw_flags = UpdateFlags::UPDATE_NONE | UpdateFlags::UPDATE_SKIP_ON_PLACE;
140
141 for pos in [center, center.north(), center.south(), center.east()] {
142 world.set_block(
143 pos.below(),
144 vanilla_blocks::STONE.default_state(),
145 raw_flags,
146 );
147 }
148 for (pos, shape) in [
149 (center.north(), RailShape::NorthSouth),
150 (center.south(), RailShape::NorthSouth),
151 (center.east(), RailShape::EastWest),
152 ] {
153 world.set_block(
154 pos,
155 vanilla_blocks::RAIL
156 .default_state()
157 .set_value(RAIL_SHAPE, shape),
158 raw_flags,
159 );
160 }
161 let state = vanilla_blocks::RAIL
162 .default_state()
163 .set_value(RAIL_SHAPE, RailShape::SouthEast);
164 world.set_block(center, state, raw_flags);
165 world.set_block(
166 center.west(),
167 vanilla_blocks::REDSTONE_BLOCK.default_state(),
168 raw_flags,
169 );
170
171 BLOCK_BEHAVIORS
172 .get_behavior(&vanilla_blocks::RAIL)
173 .handle_neighbor_changed(state, world, center, &vanilla_blocks::REDSTONE_BLOCK, false);
174
175 assert_eq!(
176 world.get_block_state(center).get_value(RAIL_SHAPE),
177 RailShape::NorthEast
178 );
179 }
180}