385 lines
11 KiB
C++
385 lines
11 KiB
C++
#include "BeltSystem.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "Tick.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
std::pair<int, int> 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<ItemType>& filterA,
|
|
const std::vector<ItemType>& filterB)
|
|
{
|
|
const std::map<std::pair<int, int>, 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<std::pair<int, int>, BeltTile>::iterator it = m_belts.find(key(tile));
|
|
if (it == m_belts.end())
|
|
{
|
|
return false;
|
|
}
|
|
return tryPlaceOnBelt(tile, item);
|
|
}
|
|
|
|
std::optional<Item> BeltSystem::tryTakeItem(Port port)
|
|
{
|
|
const std::map<std::pair<int, int>, 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<ItemType> BeltSystem::peekItem(Port port) const
|
|
{
|
|
const std::map<std::pair<int, int>, 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<QPoint>& tiles)
|
|
{
|
|
for (const QPoint& tile : tiles)
|
|
{
|
|
const std::map<std::pair<int, int>, 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<std::pair<int, int>, 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<std::pair<int, int>, 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<std::pair<int, int>, 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<std::pair<int, int>, BeltTile>::iterator nextBelt = m_belts.find(key(next));
|
|
const std::map<std::pair<int, int>, 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<std::pair<int, int>, 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<std::pair<int, int>, 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<void(VisualItem)> visit) const
|
|
{
|
|
for (const std::pair<const std::pair<int, int>, 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);
|
|
}
|
|
}
|
|
}
|