#include "BeltSystem.h" #include #include "Tick.h" // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- std::pair BeltSystem::key(QPoint tile) { return {tile.x(), tile.y()}; } QPoint BeltSystem::adjacentTile(QPoint tile, Rotation dir) { switch (dir) { case Rotation::North: return {tile.x(), tile.y() - 1}; case Rotation::East: return {tile.x() + 1, tile.y() }; case Rotation::South: return {tile.x(), tile.y() + 1}; case Rotation::West: return {tile.x() - 1, tile.y() }; } return tile; } QPointF BeltSystem::slotWorldPos(QPoint tile, Rotation dir, double progress) { // Map progress [0, 1] along the belt direction to a fractional tile-unit position. // Progress 0 = entered from opposite side; 1 = at output edge. double baseX = tile.x() + 0.5; double baseY = tile.y() + 0.5; switch (dir) { case Rotation::North: return {baseX, baseY - (progress - 0.5)}; case Rotation::East: return {baseX + (progress - 0.5), baseY}; case Rotation::South: return {baseX, baseY + (progress - 0.5)}; case Rotation::West: return {baseX - (progress - 0.5), baseY}; } return {baseX, baseY}; } // --------------------------------------------------------------------------- // Construction / placement // --------------------------------------------------------------------------- BeltSystem::BeltSystem(double beltSpeedTilesPerSecond) : m_progressPerTick(beltSpeedTilesPerSecond * kTickDurationSeconds) { } void BeltSystem::placeBelt(QPoint tile, Rotation direction) { m_splitters.erase(key(tile)); BeltTile bt; bt.direction = direction; m_belts[key(tile)] = bt; } void BeltSystem::placeSplitter(QPoint tile, Rotation outputA, Rotation outputB) { m_belts.erase(key(tile)); SplitterTile st; st.outputA = outputA; st.outputB = outputB; st.nextOutputIsA = true; m_splitters[key(tile)] = st; } void BeltSystem::removeTile(QPoint tile) { m_belts.erase(key(tile)); m_splitters.erase(key(tile)); } void BeltSystem::setSplitterFilters(QPoint tile, const std::vector& filterA, const std::vector& filterB) { const std::map, SplitterTile>::iterator it = m_splitters.find(key(tile)); if (it == m_splitters.end()) { return; } it->second.filterA = filterA; it->second.filterB = filterB; } // --------------------------------------------------------------------------- // Port interface // --------------------------------------------------------------------------- bool BeltSystem::tryPutItem(QPoint tile, Item item) { const std::map, BeltTile>::iterator it = m_belts.find(key(tile)); if (it == m_belts.end()) { return false; } return tryPlaceOnBelt(tile, item); } std::optional BeltSystem::tryTakeItem(Port port) { const std::map, BeltTile>::iterator it = m_belts.find(key(port.tile)); if (it == m_belts.end()) { return std::nullopt; } if (it->second.direction != port.direction) { return std::nullopt; } BeltTile& bt = it->second; if (bt.front && bt.front->progress >= 1.0) { const Item taken = bt.front->item; bt.front = bt.back; bt.back = std::nullopt; return taken; } return std::nullopt; } std::optional BeltSystem::peekItem(Port port) const { const std::map, BeltTile>::const_iterator it = m_belts.find(key(port.tile)); if (it == m_belts.end()) { return std::nullopt; } if (it->second.direction != port.direction) { return std::nullopt; } const BeltTile& bt = it->second; if (bt.front && bt.front->progress >= 1.0) { return bt.front->item.type; } return std::nullopt; } // --------------------------------------------------------------------------- // Maintenance // --------------------------------------------------------------------------- void BeltSystem::clearTiles(const std::vector& tiles) { for (const QPoint& tile : tiles) { const std::map, BeltTile>::iterator bIt = m_belts.find(key(tile)); if (bIt != m_belts.end()) { bIt->second.front = std::nullopt; bIt->second.back = std::nullopt; } const std::map, SplitterTile>::iterator sIt = m_splitters.find(key(tile)); if (sIt != m_splitters.end()) { sIt->second.heldItem = std::nullopt; } } } // --------------------------------------------------------------------------- // Tick // --------------------------------------------------------------------------- void BeltSystem::tick() { advanceProgress(); moveItemsToNextTile(); routeSplitterItems(); } void BeltSystem::advanceProgress() { for (std::map, BeltTile>::iterator it = m_belts.begin(); it != m_belts.end(); ++it) { BeltTile& bt = it->second; if (bt.front) { bt.front->progress += m_progressPerTick; if (bt.front->progress > 1.0) { bt.front->progress = 1.0; } } if (bt.back) { bt.back->progress += m_progressPerTick; // Back must not overtake front. if (bt.front && bt.back->progress >= bt.front->progress) { bt.back->progress = bt.front->progress - m_progressPerTick; if (bt.back->progress < 0.0) { bt.back->progress = 0.0; } } if (bt.back->progress > 1.0) { bt.back->progress = 1.0; } } } } void BeltSystem::moveItemsToNextTile() { for (std::map, BeltTile>::iterator it = m_belts.begin(); it != m_belts.end(); ++it) { BeltTile& bt = it->second; if (!bt.front || bt.front->progress < 1.0) { continue; } const QPoint here = QPoint(it->first.first, it->first.second); const QPoint next = adjacentTile(here, bt.direction); const std::map, BeltTile>::iterator nextBelt = m_belts.find(key(next)); const std::map, SplitterTile>::iterator nextSplitter = m_splitters.find(key(next)); if (nextBelt != m_belts.end()) { if (tryPlaceOnBelt(next, bt.front->item)) { bt.front = bt.back; bt.back = std::nullopt; } // else: next belt is full — item stays blocked at progress 1.0. } else if (nextSplitter != m_splitters.end()) { if (!nextSplitter->second.heldItem) { nextSplitter->second.heldItem = bt.front->item; bt.front = bt.back; bt.back = std::nullopt; } // else: splitter busy — item stays blocked at progress 1.0. } // else: no tile registered (e.g. open space, or building input port). // Items leaving into unregistered tiles are not consumed here — the // building pull step uses tryTakeItem for that. } } void BeltSystem::routeSplitterItems() { for (std::map, SplitterTile>::iterator it = m_splitters.begin(); it != m_splitters.end(); ++it) { SplitterTile& st = it->second; if (!st.heldItem) { continue; } const Item& item = *st.heldItem; const bool matchesA = st.filterA.empty() || std::find(st.filterA.begin(), st.filterA.end(), item.type) != st.filterA.end(); const bool matchesB = st.filterB.empty() || std::find(st.filterB.begin(), st.filterB.end(), item.type) != st.filterB.end(); if (matchesA && !matchesB) { const QPoint dest = adjacentTile(QPoint(it->first.first, it->first.second), st.outputA); if (tryPlaceOnBelt(dest, item)) { st.heldItem = std::nullopt; } } else if (matchesB && !matchesA) { const QPoint dest = adjacentTile(QPoint(it->first.first, it->first.second), st.outputB); if (tryPlaceOnBelt(dest, item)) { st.heldItem = std::nullopt; } } else if (matchesA && matchesB) { // Alternation: try preferred output first, fall back to other. const Rotation preferred = st.nextOutputIsA ? st.outputA : st.outputB; const Rotation fallback = st.nextOutputIsA ? st.outputB : st.outputA; const QPoint prefDest = adjacentTile(QPoint(it->first.first, it->first.second), preferred); const QPoint fbDest = adjacentTile(QPoint(it->first.first, it->first.second), fallback); if (tryPlaceOnBelt(prefDest, item)) { st.heldItem = std::nullopt; st.nextOutputIsA = !st.nextOutputIsA; } else if (tryPlaceOnBelt(fbDest, item)) { st.heldItem = std::nullopt; // nextOutputIsA stays: preferred was blocked, so we still owe it next. } // else both blocked — item stays. } // else (!matchesA && !matchesB): stall — item stays in splitter. } } bool BeltSystem::tryPlaceOnBelt(QPoint tile, Item item) { const std::map, BeltTile>::iterator it = m_belts.find(key(tile)); if (it == m_belts.end()) { return false; } BeltTile& bt = it->second; if (!bt.front) { bt.front = BeltItemSlot{item, 0.0}; return true; } if (!bt.back) { bt.back = BeltItemSlot{item, 0.0}; // Ensure ordering invariant: front has higher progress. if (bt.back->progress > bt.front->progress) { std::swap(bt.front, bt.back); } return true; } return false; // both slots occupied } // --------------------------------------------------------------------------- // Rendering // --------------------------------------------------------------------------- void BeltSystem::forEachVisualItem(QRect viewportTiles, std::function visit) const { for (const std::pair, BeltTile>& entry : m_belts) { const QPoint tile(entry.first.first, entry.first.second); if (!viewportTiles.contains(tile)) { continue; } const BeltTile& bt = entry.second; if (bt.front) { VisualItem vi; vi.type = bt.front->item.type; vi.worldPos = slotWorldPos(tile, bt.direction, bt.front->progress); visit(vi); } if (bt.back) { VisualItem vi; vi.type = bt.back->item.type; vi.worldPos = slotWorldPos(tile, bt.direction, bt.back->progress); visit(vi); } } }