#include "vf/Rail.h" namespace vf { namespace { bool railAt(const World& world, const Vec3i& cell) { return isRailBlock(world.blockAt(cell.x, cell.y, cell.z)); } bool poweredRailAt(const World& world, const Vec3i& cell) { return world.blockAt(cell.x, cell.y, cell.z) == Block::PoweredRail; } struct Neighbours { bool n = false, s = false, e = false, w = false; bool nUp = false, sUp = false, eUp = false, wUp = false; }; void probe(const World& world, const Vec3i& pos, RailDir dir, bool& conn, bool& ascend) { Vec3i flat = pos + railDelta(dir); Vec3i up = flat + Vec3i{0, 1, 0}; Vec3i down = flat + Vec3i{0, -1, 0}; bool flatRail = railAt(world, flat); bool upRail = railAt(world, up); bool downRail = railAt(world, down); conn = flatRail || upRail || downRail; ascend = upRail && !flatRail; } Neighbours gatherNeighbours(const World& world, const Vec3i& pos) { Neighbours out; probe(world, pos, RailDir::North, out.n, out.nUp); probe(world, pos, RailDir::South, out.s, out.sUp); probe(world, pos, RailDir::East, out.e, out.eUp); probe(world, pos, RailDir::West, out.w, out.wUp); return out; } bool nextPoweredRail(const World& world, const Vec3i& cur, RailDir dir, Vec3i& out) { Vec3i candidate = cur + railDelta(dir); for (int dy : {0, 1, -1}) { Vec3i cell = candidate + Vec3i{0, dy, 0}; if (poweredRailAt(world, cell)) { out = cell; return true; } } return false; } bool railEndNeighbour(const World& world, const Vec3i& pos, RailShape shape, RailDir endDir, Vec3i& out) { Vec3i base = pos + railDelta(endDir); if (isAscending(shape) && endDir == ascendUpDir(shape)) base.y += 1; Vec3i lower = base + Vec3i{0, -1, 0}; if (railAt(world, base)) { out = base; return true; } if (railAt(world, lower)) { out = lower; return true; } return false; } } // namespace RailDir opposite(RailDir dir) { switch (dir) { case RailDir::North: return RailDir::South; case RailDir::South: return RailDir::North; case RailDir::East: return RailDir::West; case RailDir::West: return RailDir::East; } return RailDir::North; } Vec3i railDelta(RailDir dir) { switch (dir) { case RailDir::North: return {0, 0, -1}; case RailDir::South: return {0, 0, 1}; case RailDir::East: return {1, 0, 0}; case RailDir::West: return {-1, 0, 0}; } return {0, 0, 0}; } bool isAscending(RailShape shape) { return shape == RailShape::AscendNorth || shape == RailShape::AscendSouth || shape == RailShape::AscendEast || shape == RailShape::AscendWest; } RailDir ascendUpDir(RailShape shape) { switch (shape) { case RailShape::AscendNorth: return RailDir::North; case RailShape::AscendSouth: return RailDir::South; case RailShape::AscendEast: return RailDir::East; case RailShape::AscendWest: return RailDir::West; default: return RailDir::North; } } std::pair railEnds(RailShape shape) { switch (shape) { case RailShape::NorthSouth: case RailShape::AscendNorth: case RailShape::AscendSouth: return {RailDir::North, RailDir::South}; case RailShape::EastWest: case RailShape::AscendEast: case RailShape::AscendWest: return {RailDir::East, RailDir::West}; case RailShape::CurveSouthEast: return {RailDir::South, RailDir::East}; case RailShape::CurveSouthWest: return {RailDir::South, RailDir::West}; case RailShape::CurveNorthWest: return {RailDir::North, RailDir::West}; case RailShape::CurveNorthEast: return {RailDir::North, RailDir::East}; } return {RailDir::North, RailDir::South}; } RailShape inferRailShape(const World& world, const Vec3i& pos, bool switched) { Neighbours nb = gatherNeighbours(world, pos); bool canCurve = world.blockAt(pos.x, pos.y, pos.z) == Block::Rail; int count = int(nb.n) + int(nb.s) + int(nb.e) + int(nb.w); RailShape shape = RailShape::NorthSouth; if (nb.n && nb.s) shape = RailShape::NorthSouth; else if (nb.e && nb.w) shape = RailShape::EastWest; else if (canCurve && nb.s && nb.e) shape = RailShape::CurveSouthEast; else if (canCurve && nb.s && nb.w) shape = RailShape::CurveSouthWest; else if (canCurve && nb.n && nb.w) shape = RailShape::CurveNorthWest; else if (canCurve && nb.n && nb.e) shape = RailShape::CurveNorthEast; else if (nb.n || nb.s) shape = RailShape::NorthSouth; else if (nb.e || nb.w) shape = RailShape::EastWest; if (switched && canCurve && count >= 3) { if (nb.n && nb.s && nb.e) shape = RailShape::CurveSouthEast; else if (nb.n && nb.s && nb.w) shape = RailShape::CurveSouthWest; else if (nb.e && nb.w && nb.n) shape = RailShape::CurveNorthEast; else if (nb.e && nb.w && nb.s) shape = RailShape::CurveSouthEast; } if (shape == RailShape::NorthSouth) { if (nb.nUp) shape = RailShape::AscendNorth; else if (nb.sUp) shape = RailShape::AscendSouth; } else if (shape == RailShape::EastWest) { if (nb.eUp) shape = RailShape::AscendEast; else if (nb.wUp) shape = RailShape::AscendWest; } return shape; } RailStep advanceOnRail(const World& world, const Vec3i& pos, RailDir moveDir, bool switched) { RailStep step; if (!railAt(world, pos)) return step; RailShape shape = inferRailShape(world, pos, switched); std::pair ends = railEnds(shape); RailDir cameFrom = opposite(moveDir); RailDir exitDir; if (cameFrom == ends.first) exitDir = ends.second; else if (cameFrom == ends.second) exitDir = ends.first; else return step; Vec3i out; if (!railEndNeighbour(world, pos, shape, exitDir, out)) return step; step.nextPos = out; step.exitDir = exitDir; step.valid = true; return step; } std::unordered_set activePoweredRails( const World& world, const std::vector& poweredRails, const std::function& directlyPowered) { std::unordered_set active; std::vector sources; for (const Vec3i& rail : poweredRails) { if (directlyPowered(rail)) { active.insert(rail); sources.push_back(rail); } } for (const Vec3i& source : sources) { RailShape shape = inferRailShape(world, source); std::pair ends = railEnds(shape); for (RailDir dir : {ends.first, ends.second}) { Vec3i cur = source; for (int step = 1; step <= poweredRailReach; ++step) { Vec3i nxt; if (!nextPoweredRail(world, cur, dir, nxt)) break; active.insert(nxt); cur = nxt; } } } return active; } std::unordered_set activePoweredRails( const World& world, const std::vector& poweredRails, const RedstoneCircuit& circuit) { return activePoweredRails(world, poweredRails, [&](const Vec3i& p) { return circuit.poweredAt(p.x, p.y, p.z) || circuit.poweredAt(p.x, p.y - 1, p.z); }); } void RailGraph::build(const World& world, const std::vector& railCells, bool switched) { adjacency.clear(); for (const Vec3i& cell : railCells) { if (!railAt(world, cell)) continue; RailShape shape = inferRailShape(world, cell, switched); std::pair ends = railEnds(shape); std::vector links; for (RailDir end : {ends.first, ends.second}) { Vec3i out; if (railEndNeighbour(world, cell, shape, end, out)) links.push_back(out); } adjacency.emplace(cell, std::move(links)); } } std::vector RailGraph::neighbours(const Vec3i& pos) const { auto it = adjacency.find(pos); if (it == adjacency.end()) return {}; return it->second; } std::vector RailGraph::trace(const World& world, const Vec3i& start, RailDir dir, int maxSteps) const { std::vector path; std::unordered_set seen; Vec3i cur = start; RailDir heading = dir; for (int step = 0; step < maxSteps; ++step) { if (seen.count(cur)) break; path.push_back(cur); seen.insert(cur); RailStep next = advanceOnRail(world, cur, heading); if (!next.valid) break; cur = next.nextPos; heading = next.exitDir; } return path; } int RailGraph::distance(const Vec3i& a, const Vec3i& b) const { if (!contains(a) || !contains(b)) return -1; if (a == b) return 0; std::unordered_map depth; std::vector frontier{a}; depth[a] = 0; while (!frontier.empty()) { std::vector nextFrontier; for (const Vec3i& node : frontier) { int nodeDepth = depth[node]; auto it = adjacency.find(node); if (it == adjacency.end()) continue; for (const Vec3i& neighbour : it->second) { if (depth.find(neighbour) != depth.end()) continue; depth[neighbour] = nodeDepth + 1; if (neighbour == b) return nodeDepth + 1; nextFrontier.push_back(neighbour); } } frontier.swap(nextFrontier); } return -1; } } // namespace vf