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, 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 #[test]
130 fn powered_three_way_junction_uses_vanilla_curve_priority() {
131 init_vanilla_registry();
132 init_behaviors();
133 let world = fresh_test_world("rail_three_way_switch");
134 let center = BlockPos::new(8, 64, 8);
135 insert_ready_full_chunk(&world, ChunkPos::from_block_pos(center));
136 let raw_flags = UpdateFlags::UPDATE_NONE | UpdateFlags::UPDATE_SKIP_ON_PLACE;
137
138 for pos in [center, center.north(), center.south(), center.east()] {
139 world.set_block(
140 pos.below(),
141 vanilla_blocks::STONE.default_state(),
142 raw_flags,
143 );
144 }
145 for (pos, shape) in [
146 (center.north(), RailShape::NorthSouth),
147 (center.south(), RailShape::NorthSouth),
148 (center.east(), RailShape::EastWest),
149 ] {
150 world.set_block(
151 pos,
152 vanilla_blocks::RAIL
153 .default_state()
154 .set_value(&BlockStateProperties::RAIL_SHAPE, shape),
155 raw_flags,
156 );
157 }
158 let state = vanilla_blocks::RAIL
159 .default_state()
160 .set_value(&BlockStateProperties::RAIL_SHAPE, RailShape::SouthEast);
161 world.set_block(center, state, raw_flags);
162 world.set_block(
163 center.west(),
164 vanilla_blocks::REDSTONE_BLOCK.default_state(),
165 raw_flags,
166 );
167
168 BLOCK_BEHAVIORS
169 .get_behavior(&vanilla_blocks::RAIL)
170 .handle_neighbor_changed(
171 state,
172 &world,
173 center,
174 &vanilla_blocks::REDSTONE_BLOCK,
175 false,
176 );
177
178 assert_eq!(
179 world
180 .get_block_state(center)
181 .get_value(&BlockStateProperties::RAIL_SHAPE),
182 RailShape::NorthEast
183 );
184 }
185}