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