Skip to main content

steel_core/behavior/blocks/redstone/rail/
rail_block.rs

1use 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/// Vanilla ordinary rail, including ordered curve and junction switching.
15#[block_behavior]
16pub struct RailBlock {
17    base: BaseRailBlock,
18}
19
20impl RailBlock {
21    /// Creates ordinary rail behavior for `block`.
22    #[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}