Add deconstruction queue
This commit is contained in:
@@ -21,6 +21,17 @@ bool isAutoRecipeBuildingType(BuildingType type)
|
||||
|| type == BuildingType::ReprocessingPlant;
|
||||
}
|
||||
|
||||
// Belts, splitters, and tunnel ends keep their runtime data in the belt subsystem
|
||||
// rather than in the Building instance, so placing/removing them must register or
|
||||
// unregister a tile with BeltSystem.
|
||||
bool isBeltSubsystemType(BuildingType type)
|
||||
{
|
||||
return type == BuildingType::Belt
|
||||
|| type == BuildingType::Splitter
|
||||
|| type == BuildingType::TunnelEntry
|
||||
|| type == BuildingType::TunnelExit;
|
||||
}
|
||||
|
||||
// The building body tile that owns an output port, given the port's outside tile
|
||||
// (port.tile) and its facing direction. The virtual output belt occupies this tile
|
||||
// and flows toward port.tile (REQ-MAT-OUTPUT-EMERGE).
|
||||
@@ -489,9 +500,10 @@ bool BuildingSystem::isPlacementValid(BuildingType type, QPoint anchor,
|
||||
// Demolish
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int BuildingSystem::demolish(BuildingId id)
|
||||
int BuildingSystem::demolish(BuildingId id, Tick currentTick)
|
||||
{
|
||||
// Construction queue?
|
||||
// Construction site? Removed instantly with the full refund; never queued
|
||||
// for deconstruction (REQ-BLD-DEMOLISH).
|
||||
for (std::deque<ConstructionSite>::iterator it = m_constructionQueue.begin();
|
||||
it != m_constructionQueue.end();
|
||||
++it)
|
||||
@@ -512,35 +524,60 @@ int BuildingSystem::demolish(BuildingId id)
|
||||
}
|
||||
}
|
||||
|
||||
// Operational building?
|
||||
for (std::vector<Building>::iterator it = m_buildings.begin();
|
||||
it != m_buildings.end();
|
||||
++it)
|
||||
// Operational building? Append it to the deconstruction queue rather than
|
||||
// removing it now; the partial refund is credited on completion in
|
||||
// tickDeconstruction (REQ-BLD-DECON-QUEUE).
|
||||
for (Building& building : m_buildings)
|
||||
{
|
||||
if (it->id == id)
|
||||
if (building.id != id) { continue; }
|
||||
if (building.queuedForDeconstruction) { return 0; } // already queued
|
||||
|
||||
building.queuedForDeconstruction = true;
|
||||
|
||||
DeconstructionEntry entry;
|
||||
entry.id = id;
|
||||
|
||||
// A queued belt/tunnel/splitter stops transporting at once: capture a
|
||||
// splitter's filters (so an un-queue can restore them), then unregister
|
||||
// its tile, discarding items on it and re-pairing tunnels as if it were
|
||||
// gone (REQ-BLD-TUNNEL-PAIR).
|
||||
if (building.type == BuildingType::Splitter)
|
||||
{
|
||||
if (it->type == BuildingType::Belt || it->type == BuildingType::Splitter
|
||||
|| it->type == BuildingType::TunnelEntry || it->type == BuildingType::TunnelExit)
|
||||
if (const std::optional<BeltSystem::SplitterInfo> info =
|
||||
m_belts.getSplitterInfo(building.anchor))
|
||||
{
|
||||
m_belts.removeTile(it->anchor);
|
||||
entry.splitterFilterA = info->filterA;
|
||||
entry.splitterFilterB = info->filterB;
|
||||
}
|
||||
const BuildingDef* def = findBuildingDef(it->type);
|
||||
for (const QPoint& cell : it->bodyCells)
|
||||
{
|
||||
m_tileOccupancy.erase({cell.x(), cell.y()});
|
||||
}
|
||||
m_buildings.erase(it);
|
||||
if (def)
|
||||
{
|
||||
return def->cost * m_config.world.refundPercentage / 100;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (isBeltSubsystemType(building.type))
|
||||
{
|
||||
m_belts.removeTile(building.anchor);
|
||||
}
|
||||
|
||||
const bool wasEmpty = m_deconstructionQueue.empty();
|
||||
m_deconstructionQueue.push_back(std::move(entry));
|
||||
if (wasEmpty)
|
||||
{
|
||||
startFrontDeconstruction(currentTick);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BuildingSystem::startFrontDeconstruction(Tick currentTick)
|
||||
{
|
||||
if (m_deconstructionQueue.empty()) { return; }
|
||||
DeconstructionEntry& front = m_deconstructionQueue.front();
|
||||
if (front.completesAt == 0)
|
||||
{
|
||||
front.completesAt =
|
||||
currentTick + secondsToTicks(m_config.world.deconstructionTimeSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Set recipe
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -780,31 +817,9 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
||||
}
|
||||
}
|
||||
|
||||
// Register with BeltSystem before the move (mask stays valid).
|
||||
if (front.type == BuildingType::Belt)
|
||||
{
|
||||
m_belts.placeBelt(front.anchor, front.rotation);
|
||||
}
|
||||
else if (front.type == BuildingType::Splitter)
|
||||
{
|
||||
assert(mask.outputPorts.size() >= 2);
|
||||
m_belts.placeSplitter(front.anchor,
|
||||
mask.outputPorts[0].direction,
|
||||
mask.outputPorts[1].direction);
|
||||
// Carry over any filters configured while under construction
|
||||
// (REQ-BLD-SITE-CONFIG).
|
||||
m_belts.setSplitterFilters(front.anchor,
|
||||
front.splitterFilterA,
|
||||
front.splitterFilterB);
|
||||
}
|
||||
else if (front.type == BuildingType::TunnelEntry)
|
||||
{
|
||||
m_belts.placeTunnelEntry(front.anchor, front.rotation, m_config.world.tunnelMaxDistance_tiles);
|
||||
}
|
||||
else if (front.type == BuildingType::TunnelExit)
|
||||
{
|
||||
m_belts.placeTunnelExit(front.anchor, front.rotation);
|
||||
}
|
||||
// Register with BeltSystem before the move (mask/building stays valid). Any
|
||||
// filters configured while under construction carry over (REQ-BLD-SITE-CONFIG).
|
||||
reregisterBeltTile(building, front.splitterFilterA, front.splitterFilterB);
|
||||
|
||||
m_buildings.push_back(std::move(building));
|
||||
|
||||
@@ -822,6 +837,114 @@ void BuildingSystem::tickConstruction(Tick currentTick)
|
||||
}
|
||||
}
|
||||
|
||||
void BuildingSystem::reregisterBeltTile(const Building& building,
|
||||
const std::vector<ItemType>& splitterFilterA,
|
||||
const std::vector<ItemType>& splitterFilterB)
|
||||
{
|
||||
switch (building.type)
|
||||
{
|
||||
case BuildingType::Belt:
|
||||
m_belts.placeBelt(building.anchor, building.rotation);
|
||||
break;
|
||||
case BuildingType::Splitter:
|
||||
assert(building.outputPorts.size() >= 2);
|
||||
m_belts.placeSplitter(building.anchor,
|
||||
building.outputPorts[0].direction,
|
||||
building.outputPorts[1].direction);
|
||||
m_belts.setSplitterFilters(building.anchor, splitterFilterA, splitterFilterB);
|
||||
break;
|
||||
case BuildingType::TunnelEntry:
|
||||
m_belts.placeTunnelEntry(building.anchor, building.rotation,
|
||||
m_config.world.tunnelMaxDistance_tiles);
|
||||
break;
|
||||
case BuildingType::TunnelExit:
|
||||
m_belts.placeTunnelExit(building.anchor, building.rotation);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BuildingSystem::tickDeconstruction(Tick currentTick)
|
||||
{
|
||||
TRACE();
|
||||
if (m_deconstructionQueue.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeconstructionEntry& front = m_deconstructionQueue.front();
|
||||
|
||||
// Guard: if the front entry's timer was never started, start it now.
|
||||
if (front.completesAt == 0)
|
||||
{
|
||||
startFrontDeconstruction(currentTick);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTick < front.completesAt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the building from the world and credit its refund (REQ-BLD-DEMOLISH).
|
||||
// Belt/tunnel/splitter tiles were already unregistered when the building was
|
||||
// queued (see demolish), so only tile occupancy and the record remain.
|
||||
for (std::vector<Building>::iterator it = m_buildings.begin();
|
||||
it != m_buildings.end();
|
||||
++it)
|
||||
{
|
||||
if (it->id != front.id) { continue; }
|
||||
|
||||
const BuildingDef* def = findBuildingDef(it->type);
|
||||
for (const QPoint& cell : it->bodyCells)
|
||||
{
|
||||
m_tileOccupancy.erase({cell.x(), cell.y()});
|
||||
}
|
||||
m_buildings.erase(it);
|
||||
if (def)
|
||||
{
|
||||
m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
m_deconstructionQueue.pop_front();
|
||||
|
||||
// Start the next queued deconstruction, if any.
|
||||
startFrontDeconstruction(currentTick);
|
||||
}
|
||||
|
||||
void BuildingSystem::cancelDeconstruction(BuildingId id)
|
||||
{
|
||||
for (std::deque<DeconstructionEntry>::iterator it = m_deconstructionQueue.begin();
|
||||
it != m_deconstructionQueue.end();
|
||||
++it)
|
||||
{
|
||||
if (it->id != id) { continue; }
|
||||
|
||||
// Resume operation: clear the flag and re-register belt/tunnel/splitter
|
||||
// tiles that were unregistered at enqueue (which re-pairs tunnels,
|
||||
// REQ-BLD-TUNNEL-PAIR). Deconstruction progress is discarded; no refund.
|
||||
if (Building* building = findBuildingMutable(id))
|
||||
{
|
||||
building->queuedForDeconstruction = false;
|
||||
reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB);
|
||||
}
|
||||
|
||||
m_deconstructionQueue.erase(it);
|
||||
// If the running front was removed, the new front (completesAt == 0) has
|
||||
// its timer started by the next tickDeconstruction guard.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool BuildingSystem::isQueuedForDeconstruction(BuildingId id) const
|
||||
{
|
||||
const Building* building = findBuilding(id);
|
||||
return building && building->queuedForDeconstruction;
|
||||
}
|
||||
|
||||
void BuildingSystem::tickBeltPull()
|
||||
{
|
||||
TRACE();
|
||||
@@ -831,6 +954,9 @@ void BuildingSystem::tickBeltPull()
|
||||
|
||||
for (Building& building : m_buildings)
|
||||
{
|
||||
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
|
||||
if (building.queuedForDeconstruction) { continue; }
|
||||
|
||||
const bool isHq = (building.type == BuildingType::Hq);
|
||||
|
||||
// 1. Advance every input belt and deliver arrivals (progress >= 0.5) into
|
||||
@@ -922,6 +1048,10 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId,
|
||||
{
|
||||
return false; // an unbuilt construction site, or not an operational building
|
||||
}
|
||||
if (consumer->queuedForDeconstruction)
|
||||
{
|
||||
return false; // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE)
|
||||
}
|
||||
|
||||
// The coupling is the consumer input port meeting this output port: same flow
|
||||
// direction, feeding the producer's output-port tile (REQ-MAT-DIRECT-COUPLE).
|
||||
@@ -943,6 +1073,9 @@ void BuildingSystem::tickProduction(Tick currentTick)
|
||||
TRACE();
|
||||
for (Building& building : m_buildings)
|
||||
{
|
||||
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
|
||||
if (building.queuedForDeconstruction) { continue; }
|
||||
|
||||
// Skip types without a recipe-based production loop.
|
||||
if (building.type == BuildingType::Belt ||
|
||||
building.type == BuildingType::Splitter ||
|
||||
@@ -1043,6 +1176,9 @@ void BuildingSystem::tickShipyardProduction(Tick currentTick)
|
||||
TRACE();
|
||||
for (Building& building : m_buildings)
|
||||
{
|
||||
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
|
||||
if (building.queuedForDeconstruction) { continue; }
|
||||
|
||||
if (building.type != BuildingType::Shipyard)
|
||||
{
|
||||
continue;
|
||||
@@ -1140,6 +1276,9 @@ void BuildingSystem::tickOutputBelts()
|
||||
|
||||
for (Building& building : m_buildings)
|
||||
{
|
||||
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
|
||||
if (building.queuedForDeconstruction) { continue; }
|
||||
|
||||
for (std::size_t p = 0; p < building.outputPorts.size(); ++p)
|
||||
{
|
||||
const Port& port = building.outputPorts[p];
|
||||
@@ -1647,6 +1786,10 @@ bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (bay->queuedForDeconstruction)
|
||||
{
|
||||
return false; // queued for deconstruction: stopped operating (REQ-BLD-DECON-QUEUE)
|
||||
}
|
||||
// Emerging scrap still counts against the bay's holding capacity
|
||||
// (REQ-MAT-OUTPUT-EMERGE).
|
||||
if (bay->getOutputItemCount() >= bay->outputBuffer.capacity)
|
||||
@@ -1820,6 +1963,7 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
|
||||
appendItems(hasher, b.production->chosenOutputs);
|
||||
}
|
||||
hasher.append(b.shipLayout.has_value());
|
||||
hasher.append(b.queuedForDeconstruction);
|
||||
}
|
||||
|
||||
hasher.append(m_constructionQueue.size());
|
||||
@@ -1840,6 +1984,17 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
|
||||
for (const ItemType& type : s.splitterFilterB) { hasher.append(type.id); }
|
||||
}
|
||||
|
||||
hasher.append(m_deconstructionQueue.size());
|
||||
for (const DeconstructionEntry& e : m_deconstructionQueue)
|
||||
{
|
||||
hasher.append(e.id);
|
||||
hasher.append(e.completesAt);
|
||||
hasher.append(e.splitterFilterA.size());
|
||||
for (const ItemType& type : e.splitterFilterA) { hasher.append(type.id); }
|
||||
hasher.append(e.splitterFilterB.size());
|
||||
for (const ItemType& type : e.splitterFilterB) { hasher.append(type.id); }
|
||||
}
|
||||
|
||||
// std::map iterates in sorted key order.
|
||||
hasher.append(m_tileOccupancy.size());
|
||||
for (const std::pair<const std::pair<int, int>, BuildingId>& entry : m_tileOccupancy)
|
||||
|
||||
Reference in New Issue
Block a user