implement tunnels
This commit is contained in:
@@ -70,10 +70,39 @@ void BeltSystem::placeSplitter(QPoint tile, Rotation outputA, Rotation outputB)
|
||||
m_splitters[key(tile)] = st;
|
||||
}
|
||||
|
||||
void BeltSystem::removeTile(QPoint tile)
|
||||
void BeltSystem::placeTunnelEntry(QPoint tile, Rotation direction, int maxDistance)
|
||||
{
|
||||
m_belts.erase(key(tile));
|
||||
m_splitters.erase(key(tile));
|
||||
m_tunnelExits.erase(key(tile));
|
||||
TunnelEntryTile te;
|
||||
te.direction = direction;
|
||||
te.maxDistance = maxDistance;
|
||||
m_tunnelEntries[key(tile)] = te;
|
||||
reevaluateTunnelPairing();
|
||||
}
|
||||
|
||||
void BeltSystem::placeTunnelExit(QPoint tile, Rotation direction)
|
||||
{
|
||||
m_belts.erase(key(tile));
|
||||
m_splitters.erase(key(tile));
|
||||
m_tunnelEntries.erase(key(tile));
|
||||
TunnelExitTile tx;
|
||||
tx.direction = direction;
|
||||
m_tunnelExits[key(tile)] = tx;
|
||||
reevaluateTunnelPairing();
|
||||
}
|
||||
|
||||
void BeltSystem::removeTile(QPoint tile)
|
||||
{
|
||||
const bool wasTunnel = (m_tunnelEntries.erase(key(tile)) > 0)
|
||||
| (m_tunnelExits.erase(key(tile)) > 0);
|
||||
m_belts.erase(key(tile));
|
||||
m_splitters.erase(key(tile));
|
||||
if (wasTunnel)
|
||||
{
|
||||
reevaluateTunnelPairing();
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::setSplitterFilters(QPoint tile,
|
||||
@@ -105,18 +134,98 @@ std::optional<BeltSystem::SplitterInfo> BeltSystem::getSplitterInfo(QPoint tile)
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tunnel pairing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void BeltSystem::reevaluateTunnelPairing()
|
||||
{
|
||||
std::vector<TunnelLink> oldLinks;
|
||||
std::swap(oldLinks, m_tunnelLinks);
|
||||
|
||||
for (const std::pair<const std::pair<int, int>, TunnelEntryTile>& entry : m_tunnelEntries)
|
||||
{
|
||||
const QPoint entryPos(entry.first.first, entry.first.second);
|
||||
const Rotation dir = entry.second.direction;
|
||||
const int maxDist = entry.second.maxDistance;
|
||||
|
||||
for (int d = 1; d <= maxDist; ++d)
|
||||
{
|
||||
QPoint probe = entryPos;
|
||||
for (int step = 0; step < d; ++step)
|
||||
{
|
||||
probe = adjacentTile(probe, dir);
|
||||
}
|
||||
|
||||
// Check if a same-direction tunnel entry is here (blocks pairing)
|
||||
const std::map<std::pair<int, int>, TunnelEntryTile>::const_iterator teIt =
|
||||
m_tunnelEntries.find(key(probe));
|
||||
if (teIt != m_tunnelEntries.end() && teIt->second.direction == dir)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if a same-direction tunnel exit is here (forms pair)
|
||||
const std::map<std::pair<int, int>, TunnelExitTile>::const_iterator txIt =
|
||||
m_tunnelExits.find(key(probe));
|
||||
if (txIt != m_tunnelExits.end())
|
||||
{
|
||||
if (txIt->second.direction == dir)
|
||||
{
|
||||
TunnelLink link;
|
||||
link.entryTile = entryPos;
|
||||
link.exitTile = probe;
|
||||
link.length = static_cast<double>(d);
|
||||
|
||||
for (const TunnelLink& old : oldLinks)
|
||||
{
|
||||
if (old.entryTile == entryPos && old.exitTile == probe)
|
||||
{
|
||||
link.items = old.items;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_tunnelLinks.push_back(std::move(link));
|
||||
break;
|
||||
}
|
||||
// Different direction exit — skip, keep searching
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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())
|
||||
const std::map<std::pair<int, int>, BeltTile>::iterator bIt = m_belts.find(key(tile));
|
||||
if (bIt != m_belts.end())
|
||||
{
|
||||
return tryPlaceOnBelt(tile, item);
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator teIt =
|
||||
m_tunnelEntries.find(key(tile));
|
||||
if (teIt != m_tunnelEntries.end())
|
||||
{
|
||||
TunnelEntryTile& te = teIt->second;
|
||||
if (!te.front)
|
||||
{
|
||||
te.front = BeltItemSlot{item, 0.0};
|
||||
return true;
|
||||
}
|
||||
if (!te.back)
|
||||
{
|
||||
te.back = BeltItemSlot{item, 0.0};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return tryPlaceOnBelt(tile, item);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<Item> BeltSystem::tryTakeItem(Port port)
|
||||
@@ -158,6 +267,20 @@ std::optional<Item> BeltSystem::tryTakeItem(Port port)
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
||||
m_tunnelExits.find(key(port.tile));
|
||||
if (txIt != m_tunnelExits.end())
|
||||
{
|
||||
TunnelExitTile& tx = txIt->second;
|
||||
if (tx.direction == port.direction && tx.front && tx.front->progress >= 1.0)
|
||||
{
|
||||
const Item taken = tx.front->item;
|
||||
tx.front = tx.back;
|
||||
tx.back = std::nullopt;
|
||||
return taken;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -194,6 +317,17 @@ std::optional<ItemType> BeltSystem::peekItem(Port port) const
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelExitTile>::const_iterator txIt =
|
||||
m_tunnelExits.find(key(port.tile));
|
||||
if (txIt != m_tunnelExits.end())
|
||||
{
|
||||
const TunnelExitTile& tx = txIt->second;
|
||||
if (tx.direction == port.direction && tx.front && tx.front->progress >= 1.0)
|
||||
{
|
||||
return tx.front->item.type;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -219,6 +353,36 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
||||
sIt->second.frontA = std::nullopt;
|
||||
sIt->second.frontB = std::nullopt;
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator teIt =
|
||||
m_tunnelEntries.find(key(tile));
|
||||
if (teIt != m_tunnelEntries.end())
|
||||
{
|
||||
teIt->second.front = std::nullopt;
|
||||
teIt->second.back = std::nullopt;
|
||||
for (TunnelLink& link : m_tunnelLinks)
|
||||
{
|
||||
if (link.entryTile == tile)
|
||||
{
|
||||
link.items.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
||||
m_tunnelExits.find(key(tile));
|
||||
if (txIt != m_tunnelExits.end())
|
||||
{
|
||||
txIt->second.front = std::nullopt;
|
||||
txIt->second.back = std::nullopt;
|
||||
for (TunnelLink& link : m_tunnelLinks)
|
||||
{
|
||||
if (link.exitTile == tile)
|
||||
{
|
||||
link.items.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +393,9 @@ void BeltSystem::clearTiles(const std::vector<QPoint>& tiles)
|
||||
void BeltSystem::tick()
|
||||
{
|
||||
advanceProgress();
|
||||
advanceTunnelProgress();
|
||||
moveItemsToNextTile();
|
||||
moveTunnelItems();
|
||||
routeSplitterItems();
|
||||
}
|
||||
|
||||
@@ -304,6 +470,98 @@ void BeltSystem::advanceProgress()
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::advanceTunnelProgress()
|
||||
{
|
||||
for (std::map<std::pair<int, int>, TunnelEntryTile>::iterator it = m_tunnelEntries.begin();
|
||||
it != m_tunnelEntries.end(); ++it)
|
||||
{
|
||||
TunnelEntryTile& te = it->second;
|
||||
|
||||
if (te.front)
|
||||
{
|
||||
te.front->progress += m_progressPerTick;
|
||||
if (te.front->progress > 1.0)
|
||||
{
|
||||
te.front->progress = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (te.back)
|
||||
{
|
||||
te.back->progress += m_progressPerTick;
|
||||
if (te.front && te.back->progress >= te.front->progress)
|
||||
{
|
||||
te.back->progress = te.front->progress - m_progressPerTick;
|
||||
if (te.back->progress < 0.0)
|
||||
{
|
||||
te.back->progress = 0.0;
|
||||
}
|
||||
}
|
||||
if (te.back->progress > 0.5)
|
||||
{
|
||||
te.back->progress = 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (std::map<std::pair<int, int>, TunnelExitTile>::iterator it = m_tunnelExits.begin();
|
||||
it != m_tunnelExits.end(); ++it)
|
||||
{
|
||||
TunnelExitTile& tx = it->second;
|
||||
|
||||
if (tx.front)
|
||||
{
|
||||
tx.front->progress += m_progressPerTick;
|
||||
if (tx.front->progress > 1.0)
|
||||
{
|
||||
tx.front->progress = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.back)
|
||||
{
|
||||
tx.back->progress += m_progressPerTick;
|
||||
if (tx.front && tx.back->progress >= tx.front->progress)
|
||||
{
|
||||
tx.back->progress = tx.front->progress - m_progressPerTick;
|
||||
if (tx.back->progress < 0.0)
|
||||
{
|
||||
tx.back->progress = 0.0;
|
||||
}
|
||||
}
|
||||
if (tx.back->progress > 0.5)
|
||||
{
|
||||
tx.back->progress = 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (TunnelLink& link : m_tunnelLinks)
|
||||
{
|
||||
for (std::size_t i = 0; i < link.items.size(); ++i)
|
||||
{
|
||||
TunnelTransitItem& ti = link.items[i];
|
||||
ti.progress += m_progressPerTick;
|
||||
if (ti.progress > link.length)
|
||||
{
|
||||
ti.progress = link.length;
|
||||
}
|
||||
if (i > 0)
|
||||
{
|
||||
const double maxProgress = link.items[i - 1].progress - 0.5;
|
||||
if (ti.progress > maxProgress)
|
||||
{
|
||||
ti.progress = maxProgress;
|
||||
if (ti.progress < 0.0)
|
||||
{
|
||||
ti.progress = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::moveItemsToNextTile()
|
||||
{
|
||||
// Belt items advancing into the next tile.
|
||||
@@ -340,11 +598,25 @@ void BeltSystem::moveItemsToNextTile()
|
||||
bt.front = bt.back;
|
||||
bt.back = std::nullopt;
|
||||
}
|
||||
// else: splitter back occupied — 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.
|
||||
else
|
||||
{
|
||||
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator nextEntry =
|
||||
m_tunnelEntries.find(key(next));
|
||||
if (nextEntry != m_tunnelEntries.end() && !nextEntry->second.back)
|
||||
{
|
||||
if (!nextEntry->second.front)
|
||||
{
|
||||
nextEntry->second.front = BeltItemSlot{bt.front->item, 0.0};
|
||||
}
|
||||
else
|
||||
{
|
||||
nextEntry->second.back = BeltItemSlot{bt.front->item, 0.0};
|
||||
}
|
||||
bt.front = bt.back;
|
||||
bt.back = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Splitter front slots advancing into downstream belt tiles.
|
||||
@@ -357,23 +629,89 @@ void BeltSystem::moveItemsToNextTile()
|
||||
if (st.frontA && st.frontA->progress >= 1.0)
|
||||
{
|
||||
const QPoint dest = adjacentTile(here, st.outputA);
|
||||
if (tryPlaceOnBelt(dest, st.frontA->item))
|
||||
if (tryPushToTile(dest, st.frontA->item, st.outputA))
|
||||
{
|
||||
st.frontA = std::nullopt;
|
||||
}
|
||||
// else: downstream belt full or absent — item stays at progress 1.0
|
||||
// for building pickup via tryTakeItem.
|
||||
}
|
||||
|
||||
if (st.frontB && st.frontB->progress >= 1.0)
|
||||
{
|
||||
const QPoint dest = adjacentTile(here, st.outputB);
|
||||
if (tryPlaceOnBelt(dest, st.frontB->item))
|
||||
if (tryPushToTile(dest, st.frontB->item, st.outputB))
|
||||
{
|
||||
st.frontB = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tunnel exit items advancing into downstream tiles.
|
||||
for (std::map<std::pair<int, int>, TunnelExitTile>::iterator it = m_tunnelExits.begin();
|
||||
it != m_tunnelExits.end(); ++it)
|
||||
{
|
||||
TunnelExitTile& tx = it->second;
|
||||
if (!tx.front || tx.front->progress < 1.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const QPoint here = QPoint(it->first.first, it->first.second);
|
||||
const QPoint next = adjacentTile(here, tx.direction);
|
||||
if (tryPushToTile(next, tx.front->item, tx.direction))
|
||||
{
|
||||
tx.front = tx.back;
|
||||
tx.back = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::moveTunnelItems()
|
||||
{
|
||||
for (TunnelLink& link : m_tunnelLinks)
|
||||
{
|
||||
// Entry front → transit
|
||||
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator teIt =
|
||||
m_tunnelEntries.find(key(link.entryTile));
|
||||
if (teIt != m_tunnelEntries.end())
|
||||
{
|
||||
TunnelEntryTile& te = teIt->second;
|
||||
if (te.front && te.front->progress >= 1.0)
|
||||
{
|
||||
const bool canEnter = link.items.empty()
|
||||
|| link.items.back().progress >= 0.5;
|
||||
if (canEnter)
|
||||
{
|
||||
TunnelTransitItem ti;
|
||||
ti.item = te.front->item;
|
||||
ti.progress = 0.0;
|
||||
link.items.push_back(ti);
|
||||
te.front = te.back;
|
||||
te.back = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transit front → exit back
|
||||
if (!link.items.empty() && link.items.front().progress >= link.length)
|
||||
{
|
||||
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
||||
m_tunnelExits.find(key(link.exitTile));
|
||||
if (txIt != m_tunnelExits.end())
|
||||
{
|
||||
TunnelExitTile& tx = txIt->second;
|
||||
if (!tx.back && !tx.front)
|
||||
{
|
||||
tx.front = BeltItemSlot{link.items.front().item, 0.0};
|
||||
link.items.erase(link.items.begin());
|
||||
}
|
||||
else if (!tx.back && tx.front)
|
||||
{
|
||||
tx.back = BeltItemSlot{link.items.front().item, 0.0};
|
||||
link.items.erase(link.items.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeltSystem::routeSplitterItems()
|
||||
@@ -474,6 +812,65 @@ bool BeltSystem::tryPlaceOnBelt(QPoint tile, Item item)
|
||||
return false; // both slots occupied
|
||||
}
|
||||
|
||||
bool BeltSystem::tryPushToTile(QPoint dest, Item item, Rotation fromDir)
|
||||
{
|
||||
if (tryPlaceOnBelt(dest, item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, SplitterTile>::iterator splIt =
|
||||
m_splitters.find(key(dest));
|
||||
if (splIt != m_splitters.end())
|
||||
{
|
||||
if (!splIt->second.back)
|
||||
{
|
||||
splIt->second.back = BeltItemSlot{item, 0.0};
|
||||
splIt->second.backDir = fromDir;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelEntryTile>::iterator teIt =
|
||||
m_tunnelEntries.find(key(dest));
|
||||
if (teIt != m_tunnelEntries.end())
|
||||
{
|
||||
TunnelEntryTile& te = teIt->second;
|
||||
if (!te.front)
|
||||
{
|
||||
te.front = BeltItemSlot{item, 0.0};
|
||||
return true;
|
||||
}
|
||||
if (!te.back)
|
||||
{
|
||||
te.back = BeltItemSlot{item, 0.0};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::map<std::pair<int, int>, TunnelExitTile>::iterator txIt =
|
||||
m_tunnelExits.find(key(dest));
|
||||
if (txIt != m_tunnelExits.end())
|
||||
{
|
||||
TunnelExitTile& tx = txIt->second;
|
||||
if (!tx.front)
|
||||
{
|
||||
tx.front = BeltItemSlot{item, 0.0};
|
||||
return true;
|
||||
}
|
||||
if (!tx.back)
|
||||
{
|
||||
tx.back = BeltItemSlot{item, 0.0};
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -542,4 +939,54 @@ void BeltSystem::forEachVisualItem(QRect viewportTiles,
|
||||
visit(vi);
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::pair<const std::pair<int, int>, TunnelEntryTile>& entry : m_tunnelEntries)
|
||||
{
|
||||
const QPoint tile(entry.first.first, entry.first.second);
|
||||
if (!viewportTiles.contains(tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const TunnelEntryTile& te = entry.second;
|
||||
if (te.front)
|
||||
{
|
||||
VisualItem vi;
|
||||
vi.type = te.front->item.type;
|
||||
vi.worldPos = slotWorldPos(tile, te.direction, te.front->progress);
|
||||
visit(vi);
|
||||
}
|
||||
if (te.back)
|
||||
{
|
||||
VisualItem vi;
|
||||
vi.type = te.back->item.type;
|
||||
vi.worldPos = slotWorldPos(tile, te.direction, te.back->progress);
|
||||
visit(vi);
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::pair<const std::pair<int, int>, TunnelExitTile>& entry : m_tunnelExits)
|
||||
{
|
||||
const QPoint tile(entry.first.first, entry.first.second);
|
||||
if (!viewportTiles.contains(tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const TunnelExitTile& tx = entry.second;
|
||||
if (tx.front)
|
||||
{
|
||||
VisualItem vi;
|
||||
vi.type = tx.front->item.type;
|
||||
vi.worldPos = slotWorldPos(tile, tx.direction, tx.front->progress);
|
||||
visit(vi);
|
||||
}
|
||||
if (tx.back)
|
||||
{
|
||||
VisualItem vi;
|
||||
vi.type = tx.back->item.type;
|
||||
vi.worldPos = slotWorldPos(tile, tx.direction, tx.back->progress);
|
||||
visit(vi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user