migrate every factory query off BuildingSystem onto the free functions

The eleven forwarding members added last commit are gone; callers now read the
data directly through FactoryQueries.h. Simulation and ArenaSimulation expose
getFactoryState() so the UI, the balancing view and the tests can reach it.

isQueuedForDeconstruction joined the free functions along the way — it only
reaches findBuilding, so it was state-pure too.

No facade was introduced. The chained form was the reason one looked attractive,
but rewriting sim.getBuildings().findBuilding(id) to findBuilding(sim.getFactoryState(), id)
turned out to be mechanical, and the result says which data is read rather than
which system happens to own it.

BuildingSystem.cpp is down to 1735 lines and no longer answers questions about
the factory — it only changes it. What remains on it are the mutators, the tick
phases, and the queries that also need GameConfig.

Verified with a golden-checksum capture before and after — all four sample ticks
identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
This commit is contained in:
2026-08-04 21:52:49 +02:00
parent 58e173ad5b
commit df7f60c898
20 changed files with 212 additions and 241 deletions

View File

@@ -488,6 +488,11 @@ const ArenaConfig& ArenaSimulation::getArenaConfig() const
return m_arenaConfig; return m_arenaConfig;
} }
const FactoryState& ArenaSimulation::getFactoryState() const
{
return m_factoryState;
}
const BuildingSystem& ArenaSimulation::getBuildings() const const BuildingSystem& ArenaSimulation::getBuildings() const
{ {
return *m_buildingSystem; return *m_buildingSystem;

View File

@@ -86,6 +86,7 @@ public:
const ArenaConfig& getArenaConfig() const; const ArenaConfig& getArenaConfig() const;
const BuildingSystem& getBuildings() const; const BuildingSystem& getBuildings() const;
const FactoryState& getFactoryState() const;
const ShipSystem& getShips() const; const ShipSystem& getShips() const;
const DebrisSystem& getDebrisSystem() const; const DebrisSystem& getDebrisSystem() const;
EntityAdmin& getAdmin(); EntityAdmin& getAdmin();

View File

@@ -1,4 +1,5 @@
#include "ArenaView.h" #include "ArenaView.h"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@@ -306,7 +307,7 @@ void ArenaView::drawTiles(QPainter& painter)
void ArenaView::drawBuildings(QPainter& painter) void ArenaView::drawBuildings(QPainter& painter)
{ {
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
const std::map<BuildingType, BuildingVisuals>::const_iterator it = const std::map<BuildingType, BuildingVisuals>::const_iterator it =
m_visuals->buildings.find(b.type); m_visuals->buildings.find(b.type);

View File

@@ -1,4 +1,5 @@
#include "BuildingConfig.h" #include "BuildingConfig.h"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
@@ -26,8 +27,8 @@ struct SelectedBuilding
// (the HQ and defence stations, per REQ-UI-BLUEPRINT-CREATE). // (the HQ and defence stations, per REQ-UI-BLUEPRINT-CREATE).
std::optional<SelectedBuilding> resolvePlaceable(const Simulation& sim, BuildingId id) std::optional<SelectedBuilding> resolvePlaceable(const Simulation& sim, BuildingId id)
{ {
const Building* building = sim.getBuildings().findBuilding(id); const Building* building = findBuilding(sim.getFactoryState(), id);
const ConstructionSite* site = building ? nullptr : sim.getBuildings().findSite(id); const ConstructionSite* site = building ? nullptr : findSite(sim.getFactoryState(), id);
if (!building && !site) if (!building && !site)
{ {
return std::nullopt; return std::nullopt;
@@ -52,8 +53,8 @@ std::optional<SelectedBuilding> resolvePlaceable(const Simulation& sim, Building
std::optional<BuildingConfig> readBuildingConfig(const Simulation& sim, BuildingId id) std::optional<BuildingConfig> readBuildingConfig(const Simulation& sim, BuildingId id)
{ {
const Building* building = sim.getBuildings().findBuilding(id); const Building* building = findBuilding(sim.getFactoryState(), id);
const ConstructionSite* site = building ? nullptr : sim.getBuildings().findSite(id); const ConstructionSite* site = building ? nullptr : findSite(sim.getFactoryState(), id);
if (!building && !site) if (!building && !site)
{ {
return std::nullopt; return std::nullopt;

View File

@@ -241,11 +241,11 @@ std::vector<Port> BuildingSystem::computeInputPorts(
std::vector<Port> BuildingSystem::getInputPorts(BuildingId id) const std::vector<Port> BuildingSystem::getInputPorts(BuildingId id) const
{ {
if (const Building* building = findBuilding(id)) if (const Building* building = findBuilding(m_state, id))
{ {
return building->inputPorts; return building->inputPorts;
} }
if (const ConstructionSite* site = findSite(id)) if (const ConstructionSite* site = findSite(m_state, id))
{ {
// A site stores no ports; derive its output ports from the mask (absolute) // A site stores no ports; derive its output ports from the mask (absolute)
// and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS). // and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS).
@@ -825,7 +825,7 @@ void BuildingSystem::cancelDeconstruction(BuildingId id)
// Resume operation: clear the flag and re-register belt/tunnel/splitter // Resume operation: clear the flag and re-register belt/tunnel/splitter
// tiles that were unregistered at enqueue (which re-pairs tunnels, // tiles that were unregistered at enqueue (which re-pairs tunnels,
// REQ-BLD-TUNNEL-PAIR). Deconstruction progress is discarded; no refund. // REQ-BLD-TUNNEL-PAIR). Deconstruction progress is discarded; no refund.
if (Building* building = findBuildingMutable(id)) if (Building* building = findBuilding(m_state, id))
{ {
building->queuedForDeconstruction = false; building->queuedForDeconstruction = false;
reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB); reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB);
@@ -838,12 +838,6 @@ void BuildingSystem::cancelDeconstruction(BuildingId id)
} }
} }
bool BuildingSystem::isQueuedForDeconstruction(BuildingId id) const
{
const Building* building = findBuilding(id);
return building && building->queuedForDeconstruction;
}
void BuildingSystem::tickBeltPull() void BuildingSystem::tickBeltPull()
{ {
TRACE(); TRACE();
@@ -941,7 +935,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId,
return false; return false;
} }
Building* consumer = findBuildingMutable(*ownerId); Building* consumer = findBuilding(m_state, *ownerId);
if (!consumer) if (!consumer)
{ {
return false; // an unbuilt construction site, or not an operational building return false; // an unbuilt construction site, or not an operational building
@@ -1263,41 +1257,6 @@ void BuildingSystem::forEachIncomingItem(
// Queries // Queries
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const Building* BuildingSystem::findBuilding(BuildingId id) const
{
return ::findBuilding(m_state, id);
}
Building* BuildingSystem::findBuildingMutable(BuildingId id)
{
return ::findBuilding(m_state, id);
}
const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
{
return ::findSite(m_state, id);
}
std::vector<Building> BuildingSystem::getAllBuildings() const
{
return ::getAllBuildings(m_state);
}
std::vector<ConstructionSite> BuildingSystem::getAllSites() const
{
return ::getAllSites(m_state);
}
int BuildingSystem::getProductionBuildingCount() const
{
return ::getProductionBuildingCount(m_state);
}
int BuildingSystem::getActiveProductionBuildingCount() const
{
return ::getActiveProductionBuildingCount(m_state);
}
std::vector<const RecipeDef*> std::vector<const RecipeDef*>
BuildingSystem::gatherCandidateRecipes(const Building& b) const BuildingSystem::gatherCandidateRecipes(const Building& b) const
{ {
@@ -1471,11 +1430,6 @@ std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() cons
return result; return result;
} }
bool BuildingSystem::isTileOccupied(QPoint tile) const
{
return ::isTileOccupied(m_state, tile);
}
std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget( std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
BuildingType type, QPoint anchor, Rotation rot) const BuildingType type, QPoint anchor, Rotation rot) const
{ {
@@ -1591,17 +1545,6 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
} }
} }
const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
BuildingType type) const
{
return ::findNearestBuilding(m_state, worldPos, type);
}
bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
{
return ::deliverScrapToSalvageBay(m_state, bayId);
}
BuildingId BuildingSystem::placeImmediate(BuildingType type, BuildingId BuildingSystem::placeImmediate(BuildingType type,
const std::vector<std::string>& surfaceMask, const std::vector<std::string>& surfaceMask,
QPoint anchor, Rotation rotation) QPoint anchor, Rotation rotation)

View File

@@ -94,7 +94,6 @@ public:
void cancelDeconstruction(BuildingId id); void cancelDeconstruction(BuildingId id);
// True if the building is currently in the deconstruction queue. // True if the building is currently in the deconstruction queue.
bool isQueuedForDeconstruction(BuildingId id) const;
// Set the recipe (or schematic id for shipyard) on a building or queued // Set the recipe (or schematic id for shipyard) on a building or queued
// construction site. Clears both buffers on an operational building. // construction site. Clears both buffers on an operational building.
@@ -140,18 +139,12 @@ public:
Rotation directionB; // Splitter: second output; Belt: same as directionA Rotation directionB; // Splitter: second output; Belt: same as directionA
}; };
const Building* findBuilding(BuildingId id) const;
const ConstructionSite* findSite(BuildingId id) const;
std::vector<Building> getAllBuildings() const;
std::vector<ConstructionSite> getAllSites() const;
// REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed // REQ-UI-DEBUG-OVERLAY "Max Factory Production": count of completed
// (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings. // (operational) Miner/Smelter/Assembler/ReprocessingPlant/Shipyard buildings.
int getProductionBuildingCount() const;
// REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above // REQ-UI-DEBUG-OVERLAY "Current Factory Production": subset of the above
// that currently has an active production cycle. // that currently has an active production cycle.
int getActiveProductionBuildingCount() const;
// Production state for the UI status light (REQ-UI-STATUS-LIGHT). Returns // Production state for the UI status light (REQ-UI-STATUS-LIGHT). Returns
// nullopt for building types that show no light (belts, splitters, tunnels, // nullopt for building types that show no light (belts, splitters, tunnels,
@@ -201,7 +194,6 @@ public:
// Place one "scrap" item into a SalvageBay's output buffer. // Place one "scrap" item into a SalvageBay's output buffer.
// Returns false if bay not found, wrong type, or output buffer is full. // Returns false if bay not found, wrong type, or output buffer is full.
bool deliverScrapToSalvageBay(BuildingId bayId);
// Bypass the construction queue and create a fully-operational Building // Bypass the construction queue and create a fully-operational Building
// immediately. Used for pre-placed structures (HQ, defence stations). // immediately. Used for pre-placed structures (HQ, defence stations).
@@ -234,7 +226,6 @@ private:
const std::vector<ItemType>& splitterFilterA, const std::vector<ItemType>& splitterFilterA,
const std::vector<ItemType>& splitterFilterB); const std::vector<ItemType>& splitterFilterB);
Building* findBuildingMutable(BuildingId id);
// True if the consumer would accept `type` at the given input port right now: // True if the consumer would accept `type` at the given input port right now:
// it is a required input (or a building block for the HQ), the reservation-aware // it is a required input (or a building block for the HQ), the reservation-aware
// buffer has room, and the input belt entry is free (REQ-MAT-INPUT-INTAKE). // buffer has room, and the input belt entry is free (REQ-MAT-INPUT-INTAKE).

View File

@@ -77,6 +77,12 @@ bool isTileOccupied(const FactoryState& state, QPoint tile)
return state.grid.isOccupied(tile); return state.grid.isOccupied(tile);
} }
bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id)
{
const Building* building = findBuilding(state, id);
return building && building->queuedForDeconstruction;
}
const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos, const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos,
BuildingType type) BuildingType type)
{ {

View File

@@ -39,6 +39,9 @@ int getActiveProductionBuildingCount(const FactoryState& state);
bool isTileOccupied(const FactoryState& state, QPoint tile); bool isTileOccupied(const FactoryState& state, QPoint tile);
// True while the building is in the deconstruction queue (REQ-BLD-DECON-QUEUE).
bool isQueuedForDeconstruction(const FactoryState& state, BuildingId id);
// The nearest building of the given type to a world position, or nullptr when // The nearest building of the given type to a world position, or nullptr when
// none exists. Distance is measured to the building's footprint centre. // none exists. Distance is measured to the building's footprint centre.
const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos, const Building* findNearestBuilding(const FactoryState& state, QVector2D worldPos,

View File

@@ -1,5 +1,7 @@
#include "Simulation.h" #include "Simulation.h"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
@@ -811,12 +813,12 @@ double Simulation::getThreatAccumulationRate() const
double Simulation::getMaxFactoryProductionThreatRate() const double Simulation::getMaxFactoryProductionThreatRate() const
{ {
return static_cast<double>(m_buildingSystem->getProductionBuildingCount()); return static_cast<double>(getProductionBuildingCount(m_factoryState));
} }
double Simulation::getCurrentFactoryProductionThreatRate() const double Simulation::getCurrentFactoryProductionThreatRate() const
{ {
return static_cast<double>(m_buildingSystem->getActiveProductionBuildingCount()); return static_cast<double>(getActiveProductionBuildingCount(m_factoryState));
} }
int Simulation::getBossWaveCounter() const int Simulation::getBossWaveCounter() const
@@ -895,6 +897,11 @@ BuildingSystem& Simulation::getBuildingsMutable()
return *m_buildingSystem; return *m_buildingSystem;
} }
const FactoryState& Simulation::getFactoryState() const
{
return m_factoryState;
}
const BuildingSystem& Simulation::getBuildings() const const BuildingSystem& Simulation::getBuildings() const
{ {
return *m_buildingSystem; return *m_buildingSystem;

View File

@@ -119,6 +119,9 @@ public:
// chokepoint) or, in tests, SimulationTestAccess — so production code cannot // chokepoint) or, in tests, SimulationTestAccess — so production code cannot
// mutate the factory outside the recorded command path (docs/replay_design.md). // mutate the factory outside the recorded command path (docs/replay_design.md).
const BuildingSystem& getBuildings() const; const BuildingSystem& getBuildings() const;
// The factory's world data, for the free queries in FactoryQueries.h.
const FactoryState& getFactoryState() const;
const BeltSystem& getBelts() const; const BeltSystem& getBelts() const;
ShipSystem& getShips(); ShipSystem& getShips();
const ShipSystem& getShips() const; const ShipSystem& getShips() const;

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include <cmath> #include <cmath>
#include <random> #include <random>
@@ -957,12 +958,12 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
for (int i = 0; i < 500; ++i) for (int i = 0; i < 500; ++i)
{ {
f.buildings.tickConstruction(t++); f.buildings.tickConstruction(t++);
if (f.buildings.findBuilding(bayId) != nullptr) if (findBuilding(f.state, bayId) != nullptr)
{ {
break; break;
} }
} }
REQUIRE(f.buildings.findBuilding(bayId) != nullptr); REQUIRE(findBuilding(f.state, bayId) != nullptr);
const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvager"); const ShipLayoutConfig salvageLayout = makeSingleModuleLayout("salvager");
const entt::entity ship = f.ships.spawn("salvage_ship", QVector2D(5.0f, 0.0f), const entt::entity ship = f.ships.spawn("salvage_ship", QVector2D(5.0f, 0.0f),
@@ -992,9 +993,9 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo",
for (int i = 0; i < 500; ++i) for (int i = 0; i < 500; ++i)
{ {
f.buildings.tickConstruction(t++); f.buildings.tickConstruction(t++);
if (f.buildings.findBuilding(bayId) != nullptr) { break; } if (findBuilding(f.state, bayId) != nullptr) { break; }
} }
const Building* bay = f.buildings.findBuilding(bayId); const Building* bay = findBuilding(f.state, bayId);
REQUIRE(bay != nullptr); REQUIRE(bay != nullptr);
// Config-driven output-buffer capacity is applied on placement (REQ-BLD-SALVAGE-BAY). // Config-driven output-buffer capacity is applied on placement (REQ-BLD-SALVAGE-BAY).
REQUIRE(bay->outputBuffer.capacity == 20); REQUIRE(bay->outputBuffer.capacity == 20);
@@ -1015,7 +1016,7 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo",
// One unit handed over from cargo into the bay's output buffer. // One unit handed over from cargo into the bay's output buffer.
REQUIRE(f.admin.get<CargoComponent>(ship).current == before - 1); REQUIRE(f.admin.get<CargoComponent>(ship).current == before - 1);
const Building* bayAfter = f.buildings.findBuilding(bayId); const Building* bayAfter = findBuilding(f.state, bayId);
REQUIRE(bayAfter != nullptr); REQUIRE(bayAfter != nullptr);
REQUIRE(bayAfter->outputBuffer.items.size() == 1); REQUIRE(bayAfter->outputBuffer.items.size() == 1);
REQUIRE(bayAfter->outputBuffer.items.front().type.id == "scrap"); REQUIRE(bayAfter->outputBuffer.items.front().type.id == "scrap");

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
@@ -529,9 +530,9 @@ TEST_CASE("Blueprint placement: buildings land at anchor + offset from cursor",
REQUIRE(idA != kInvalidBuildingId); REQUIRE(idA != kInvalidBuildingId);
REQUIRE(idB != kInvalidBuildingId); REQUIRE(idB != kInvalidBuildingId);
REQUIRE(sim.getBuildings().isTileOccupied(cursor + offsetA)); // (-6, 0) REQUIRE(isTileOccupied(sim.getFactoryState(), cursor + offsetA)); // (-6, 0)
REQUIRE(sim.getBuildings().isTileOccupied(cursor + offsetB)); // (-4, 0) REQUIRE(isTileOccupied(sim.getFactoryState(), cursor + offsetB)); // (-4, 0)
REQUIRE_FALSE(sim.getBuildings().isTileOccupied(cursor)); // center not occupied REQUIRE_FALSE(isTileOccupied(sim.getFactoryState(), cursor)); // center not occupied
} }
TEST_CASE("Blueprint placement: cost is deducted for each building in sequence", "[blueprint]") TEST_CASE("Blueprint placement: cost is deducted for each building in sequence", "[blueprint]")
@@ -600,7 +601,7 @@ TEST_CASE("Simulation: tryPlaceBuilding rejects terrain-invalid placement and ch
REQUIRE_FALSE(id.has_value()); REQUIRE_FALSE(id.has_value());
REQUIRE(sim.getBuildingBlocksStock() == startBlocks); REQUIRE(sim.getBuildingBlocksStock() == startBlocks);
REQUIRE(sim.getBuildings().getAllSites().empty()); REQUIRE(getAllSites(sim.getFactoryState()).empty());
} }
TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies tiles, charges cost", TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies tiles, charges cost",
@@ -623,11 +624,11 @@ TEST_CASE("Simulation: tryPlaceBuilding accepts a valid asteroid spot, occupies
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildingBlocksStock() == startBlocks - minerCost); REQUIRE(sim.getBuildingBlocksStock() == startBlocks - minerCost);
REQUIRE(sim.getBuildings().isTileOccupied(QPoint(-3, 0))); REQUIRE(isTileOccupied(sim.getFactoryState(), QPoint(-3, 0)));
REQUIRE(sim.getBuildings().isTileOccupied(QPoint(-2, 0))); REQUIRE(isTileOccupied(sim.getFactoryState(), QPoint(-2, 0)));
REQUIRE(sim.getBuildings().isTileOccupied(QPoint(-3, 1))); REQUIRE(isTileOccupied(sim.getFactoryState(), QPoint(-3, 1)));
// The output-port tile (1,1)+anchor = (-2,1) is not a body cell. // The output-port tile (1,1)+anchor = (-2,1) is not a body cell.
REQUIRE_FALSE(sim.getBuildings().isTileOccupied(QPoint(-2, 1))); REQUIRE_FALSE(isTileOccupied(sim.getFactoryState(), QPoint(-2, 1)));
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -667,7 +668,7 @@ TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", "
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore"); SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
const ConstructionSite* site = sim.getBuildings().findSite(id); const ConstructionSite* site = findSite(sim.getFactoryState(), id);
REQUIRE(site != nullptr); REQUIRE(site != nullptr);
REQUIRE(site->recipeId == "mine_iron_ore"); REQUIRE(site->recipeId == "mine_iron_ore");
} }
@@ -688,7 +689,7 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction
sim.tick(); sim.tick();
} }
const Building* b = sim.getBuildings().findBuilding(id); const Building* b = findBuilding(sim.getFactoryState(), id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
REQUIRE(b->recipeId == "mine_copper_ore"); REQUIRE(b->recipeId == "mine_copper_ore");
} }
@@ -708,8 +709,8 @@ TEST_CASE("Blueprint creation: a construction site is captured", "[blueprint]")
const BuildingId id = const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Belt, QPoint(-2, 0), Rotation::East).value(); SimulationTestAccess::place(sim, BuildingType::Belt, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildings().findSite(id) != nullptr); REQUIRE(findSite(sim.getFactoryState(), id) != nullptr);
REQUIRE(sim.getBuildings().findBuilding(id) == nullptr); REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr);
const Blueprint bp = captureBlueprintFromSelection(sim, { id }); const Blueprint bp = captureBlueprintFromSelection(sim, { id });
@@ -744,14 +745,14 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site
REQUIRE(idA != kInvalidBuildingId); REQUIRE(idA != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idA, "mine_iron_ore"); SimulationTestAccess::buildings(sim).setRecipe(idA, "mine_iron_ore");
for (int i = 0; i <= static_cast<int>(secondsToTicks(10.0)); ++i) { sim.tick(); } for (int i = 0; i <= static_cast<int>(secondsToTicks(10.0)); ++i) { sim.tick(); }
REQUIRE(sim.getBuildings().findBuilding(idA) != nullptr); REQUIRE(findBuilding(sim.getFactoryState(), idA) != nullptr);
// Building B: place and configure, but leave as a construction site. // Building B: place and configure, but leave as a construction site.
const BuildingId idB = const BuildingId idB =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East).value(); SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East).value();
REQUIRE(idB != kInvalidBuildingId); REQUIRE(idB != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idB, "mine_copper_ore"); SimulationTestAccess::buildings(sim).setRecipe(idB, "mine_copper_ore");
REQUIRE(sim.getBuildings().findSite(idB) != nullptr); REQUIRE(findSite(sim.getFactoryState(), idB) != nullptr);
const Blueprint bp = captureBlueprintFromSelection(sim, { idA, idB }); const Blueprint bp = captureBlueprintFromSelection(sim, { idA, idB });
@@ -775,7 +776,7 @@ TEST_CASE("Blueprint creation: selectionHasPlaceableBuilding sees a construction
const BuildingId id = const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value(); SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildings().findSite(id) != nullptr); REQUIRE(findSite(sim.getFactoryState(), id) != nullptr);
REQUIRE(selectionHasPlaceableBuilding(sim, { id })); REQUIRE(selectionHasPlaceableBuilding(sim, { id }));
} }
@@ -855,7 +856,7 @@ TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout
SimulationTestAccess::buildings(sim).setShipLayout(id, layout); SimulationTestAccess::buildings(sim).setShipLayout(id, layout);
const ConstructionSite* site = sim.getBuildings().findSite(id); const ConstructionSite* site = findSite(sim.getFactoryState(), id);
REQUIRE(site != nullptr); REQUIRE(site != nullptr);
REQUIRE(site->shipLayout.has_value()); REQUIRE(site->shipLayout.has_value());
REQUIRE(site->shipLayout->placedModules.size() == 1); REQUIRE(site->shipLayout->placedModules.size() == 1);
@@ -892,7 +893,7 @@ TEST_CASE("Blueprint placement: ship layout transfers to building after construc
sim.tick(); sim.tick();
} }
const Building* b = sim.getBuildings().findBuilding(id); const Building* b = findBuilding(sim.getFactoryState(), id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
REQUIRE(b->shipLayout.has_value()); REQUIRE(b->shipLayout.has_value());
REQUIRE(b->shipLayout->placedModules.size() == 1); REQUIRE(b->shipLayout->placedModules.size() == 1);

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
@@ -122,8 +123,8 @@ TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
const BuildingId id = const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value(); SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.getBuildings().findBuilding(id) == nullptr); REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr);
REQUIRE(sim.getBuildings().findSite(id) != nullptr); REQUIRE(findSite(sim.getFactoryState(), id) != nullptr);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore"); SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include <map> #include <map>
#include <random> #include <random>
@@ -125,11 +126,11 @@ TEST_CASE("BuildingSystem: place miner occupies expected body tiles", "[building
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
// Miner mask ["AA","A>"] with East rotation → body at (0,0),(1,0),(0,1). // Miner mask ["AA","A>"] with East rotation → body at (0,0),(1,0),(0,1).
REQUIRE(bs.isTileOccupied(QPoint(0, 0))); REQUIRE(isTileOccupied(state_bs, QPoint(0, 0)));
REQUIRE(bs.isTileOccupied(QPoint(1, 0))); REQUIRE(isTileOccupied(state_bs, QPoint(1, 0)));
REQUIRE(bs.isTileOccupied(QPoint(0, 1))); REQUIRE(isTileOccupied(state_bs, QPoint(0, 1)));
// (1,1) is the output-port tile, NOT a body cell. // (1,1) is the output-port tile, NOT a body cell.
REQUIRE_FALSE(bs.isTileOccupied(QPoint(1, 1))); REQUIRE_FALSE(isTileOccupied(state_bs, QPoint(1, 1)));
} }
// -- World-bounds rejection (REQ-BLD-PLACE-VALID) --------------------------- // -- World-bounds rejection (REQ-BLD-PLACE-VALID) ---------------------------
@@ -142,8 +143,8 @@ TEST_CASE("BuildingSystem: place rejects a building above the world (y < 0)", "[
// row sits above the world. // row sits above the world.
const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner, QPoint(0, -1), Rotation::East, 0); const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner, QPoint(0, -1), Rotation::East, 0);
REQUIRE_FALSE(id.has_value()); REQUIRE_FALSE(id.has_value());
REQUIRE(f.bs.getAllSites().empty()); REQUIRE(getAllSites(f.state).empty());
REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0))); REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
} }
TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height)", "[building]") TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height)", "[building]")
@@ -156,7 +157,7 @@ TEST_CASE("BuildingSystem: place rejects a building below the world (y >= height
const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner, const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner,
QPoint(0, heightTiles - 1), Rotation::East, 0); QPoint(0, heightTiles - 1), Rotation::East, 0);
REQUIRE_FALSE(id.has_value()); REQUIRE_FALSE(id.has_value());
REQUIRE(f.bs.getAllSites().empty()); REQUIRE(getAllSites(f.state).empty());
} }
TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge", "[building]") TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge", "[building]")
@@ -167,7 +168,7 @@ TEST_CASE("BuildingSystem: place rejects a building left of the asteroid edge",
const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner, const std::optional<BuildingId> id = f.bs.place(BuildingType::Miner,
QPoint(leftEdgeX - 1, 0), Rotation::East, 0); QPoint(leftEdgeX - 1, 0), Rotation::East, 0);
REQUIRE_FALSE(id.has_value()); REQUIRE_FALSE(id.has_value());
REQUIRE(f.bs.getAllSites().empty()); REQUIRE(getAllSites(f.state).empty());
} }
TEST_CASE("BuildingSystem: place accepts a building flush against the world's left edge", TEST_CASE("BuildingSystem: place accepts a building flush against the world's left edge",
@@ -180,7 +181,7 @@ TEST_CASE("BuildingSystem: place accepts a building flush against the world's le
const BuildingId id = f.bs.place(BuildingType::Miner, const BuildingId id = f.bs.place(BuildingType::Miner,
QPoint(leftEdgeX, 0), Rotation::East, 0).value(); QPoint(leftEdgeX, 0), Rotation::East, 0).value();
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
REQUIRE(f.bs.isTileOccupied(QPoint(leftEdgeX, 0))); REQUIRE(isTileOccupied(f.state, QPoint(leftEdgeX, 0)));
} }
TEST_CASE("BuildingSystem: place imposes no right-side bound (space extends rightward)", TEST_CASE("BuildingSystem: place imposes no right-side bound (space extends rightward)",
@@ -240,9 +241,9 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem after con
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"), Rotation::East)); REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore"), Rotation::East));
REQUIRE(bs.getAllBuildings().size() == 1); REQUIRE(getAllBuildings(state_bs).size() == 1);
REQUIRE(bs.getAllBuildings()[0].type == BuildingType::Belt); REQUIRE(getAllBuildings(state_bs)[0].type == BuildingType::Belt);
REQUIRE(bs.getAllBuildings()[0].anchor == QPoint(5, 5)); REQUIRE(getAllBuildings(state_bs)[0].anchor == QPoint(5, 5));
} }
TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]") TEST_CASE("BuildingSystem: placed building enters construction queue", "[building]")
@@ -262,9 +263,9 @@ TEST_CASE("BuildingSystem: placed building enters construction queue", "[buildin
const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value(); const BuildingId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
REQUIRE(bs.getAllSites().size() == 1); REQUIRE(getAllSites(state_bs).size() == 1);
REQUIRE(bs.getAllBuildings().empty()); REQUIRE(getAllBuildings(state_bs).empty());
REQUIRE(bs.findSite(id) != nullptr); REQUIRE(findSite(state_bs, id) != nullptr);
} }
TEST_CASE("BuildingSystem: deconstructing a construction site removes it instantly with full refund", TEST_CASE("BuildingSystem: deconstructing a construction site removes it instantly with full refund",
@@ -279,8 +280,8 @@ TEST_CASE("BuildingSystem: deconstructing a construction site removes it instant
const int refund = f.bs.deconstruct(id, 0); const int refund = f.bs.deconstruct(id, 0);
REQUIRE(refund == 15); // Miner cost = 15 REQUIRE(refund == 15); // Miner cost = 15
REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0))); REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
REQUIRE(f.bs.getAllSites().empty()); REQUIRE(getAllSites(f.state).empty());
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -304,7 +305,7 @@ TEST_CASE("BuildingSystem: first queued building starts construction immediately
rng); rng);
bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0); bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
REQUIRE(bs.getAllSites().front().completesAt > 0); REQUIRE(getAllSites(state_bs).front().completesAt > 0);
} }
TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[building]") TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[building]")
@@ -325,9 +326,9 @@ TEST_CASE("BuildingSystem: second queued building waits (completesAt == 0)", "[b
bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0); bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
bs.place(BuildingType::Miner, QPoint(5, 5), Rotation::East, 0); bs.place(BuildingType::Miner, QPoint(5, 5), Rotation::East, 0);
REQUIRE(bs.getAllSites().size() == 2); REQUIRE(getAllSites(state_bs).size() == 2);
REQUIRE(bs.getAllSites()[0].completesAt > 0); REQUIRE(getAllSites(state_bs)[0].completesAt > 0);
REQUIRE(bs.getAllSites()[1].completesAt == 0); REQUIRE(getAllSites(state_bs)[1].completesAt == 0);
} }
TEST_CASE("BuildingSystem: construction completes after configured duration", "[building]") TEST_CASE("BuildingSystem: construction completes after configured duration", "[building]")
@@ -352,8 +353,8 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
Tick tick = 0; Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
REQUIRE(bs.getAllSites().empty()); REQUIRE(getAllSites(state_bs).empty());
REQUIRE(bs.findBuilding(id) != nullptr); REQUIRE(findBuilding(state_bs, id) != nullptr);
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -363,11 +364,11 @@ TEST_CASE("BuildingSystem: construction completes after configured duration", "[
// Runs ticks until the building with the given id is operational, or fails. // Runs ticks until the building with the given id is operational, or fails.
static void runUntilBuilt(PlacementFixture& f, BuildingId id, Tick& tick) static void runUntilBuilt(PlacementFixture& f, BuildingId id, Tick& tick)
{ {
for (int i = 0; i < 100000 && f.bs.findBuilding(id) == nullptr; ++i) for (int i = 0; i < 100000 && findBuilding(f.state, id) == nullptr; ++i)
{ {
runTicks(f.bs, f.belts, 1, tick); runTicks(f.bs, f.belts, 1, tick);
} }
REQUIRE(f.bs.findBuilding(id) != nullptr); REQUIRE(findBuilding(f.state, id) != nullptr);
} }
TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund credited on completion", TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund credited on completion",
@@ -384,15 +385,15 @@ TEST_CASE("BuildingSystem: deconstructing a built building queues it; refund cre
// stopping it operating while its tiles stay occupied (REQ-BLD-DECON-QUEUE). // stopping it operating while its tiles stay occupied (REQ-BLD-DECON-QUEUE).
const int refund = f.bs.deconstruct(id, tick); const int refund = f.bs.deconstruct(id, tick);
REQUIRE(refund == 0); REQUIRE(refund == 0);
REQUIRE(f.bs.isQueuedForDeconstruction(id)); REQUIRE(isQueuedForDeconstruction(f.state, id));
REQUIRE(f.bs.isTileOccupied(QPoint(0, 0))); REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
REQUIRE(f.stock == 0); REQUIRE(f.stock == 0);
// After the deconstruction time (0.1s = 3 ticks) it is removed and the partial // After the deconstruction time (0.1s = 3 ticks) it is removed and the partial
// refund (15 * 75 / 100 = 11) is credited exactly once. // refund (15 * 75 / 100 = 11) is credited exactly once.
runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick); runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick);
REQUIRE(f.bs.findBuilding(id) == nullptr); REQUIRE(findBuilding(f.state, id) == nullptr);
REQUIRE_FALSE(f.bs.isTileOccupied(QPoint(0, 0))); REQUIRE_FALSE(isTileOccupied(f.state, QPoint(0, 0)));
REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100); REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100);
} }
@@ -409,20 +410,20 @@ TEST_CASE("BuildingSystem: deconstruction queue removes one building at a time",
// Queue both in one tick; 'a' is at the front of the deconstruction queue. // Queue both in one tick; 'a' is at the front of the deconstruction queue.
f.bs.deconstruct(a, tick); f.bs.deconstruct(a, tick);
f.bs.deconstruct(b, tick); f.bs.deconstruct(b, tick);
REQUIRE(f.bs.isQueuedForDeconstruction(a)); REQUIRE(isQueuedForDeconstruction(f.state, a));
REQUIRE(f.bs.isQueuedForDeconstruction(b)); REQUIRE(isQueuedForDeconstruction(f.state, b));
// After one deconstruction interval only the front building is gone; the // After one deconstruction interval only the front building is gone; the
// second is still queued and its refund not yet credited. // second is still queued and its refund not yet credited.
runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick); runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 1, tick);
REQUIRE(f.bs.findBuilding(a) == nullptr); REQUIRE(findBuilding(f.state, a) == nullptr);
REQUIRE(f.bs.findBuilding(b) != nullptr); REQUIRE(findBuilding(f.state, b) != nullptr);
REQUIRE(f.bs.isQueuedForDeconstruction(b)); REQUIRE(isQueuedForDeconstruction(f.state, b));
REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100); REQUIRE(f.stock == 15 * f.cfg.world.refundPercentage / 100);
// The second drains next. // The second drains next.
runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 2, tick); runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 2, tick);
REQUIRE(f.bs.findBuilding(b) == nullptr); REQUIRE(findBuilding(f.state, b) == nullptr);
REQUIRE(f.stock == 2 * (15 * f.cfg.world.refundPercentage / 100)); REQUIRE(f.stock == 2 * (15 * f.cfg.world.refundPercentage / 100));
} }
@@ -437,18 +438,18 @@ TEST_CASE("BuildingSystem: cancelling deconstruction resumes the building with n
runUntilBuilt(f, id, tick); runUntilBuilt(f, id, tick);
f.bs.deconstruct(id, tick); f.bs.deconstruct(id, tick);
REQUIRE(f.bs.isQueuedForDeconstruction(id)); REQUIRE(isQueuedForDeconstruction(f.state, id));
// Un-queue before it drains: it operates again, no refund, tiles still occupied. // Un-queue before it drains: it operates again, no refund, tiles still occupied.
f.bs.cancelDeconstruction(id); f.bs.cancelDeconstruction(id);
REQUIRE_FALSE(f.bs.isQueuedForDeconstruction(id)); REQUIRE_FALSE(isQueuedForDeconstruction(f.state, id));
REQUIRE(f.bs.findBuilding(id) != nullptr); REQUIRE(findBuilding(f.state, id) != nullptr);
REQUIRE(f.bs.isTileOccupied(QPoint(0, 0))); REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
REQUIRE(f.stock == 0); REQUIRE(f.stock == 0);
// It is never removed even after more than a deconstruction interval passes. // It is never removed even after more than a deconstruction interval passes.
runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 5, tick); runTicks(f.bs, f.belts, static_cast<int>(secondsToTicks(0.1)) + 5, tick);
REQUIRE(f.bs.findBuilding(id) != nullptr); REQUIRE(findBuilding(f.state, id) != nullptr);
REQUIRE(f.stock == 0); REQUIRE(f.stock == 0);
} }
@@ -466,7 +467,7 @@ TEST_CASE("BuildingSystem: queued belt stops transporting; cancel restores it",
// accepts or transports items, though the tile stays occupied (REQ-BLD-DECON-QUEUE). // accepts or transports items, though the tile stays occupied (REQ-BLD-DECON-QUEUE).
f.bs.deconstruct(id, tick); f.bs.deconstruct(id, tick);
REQUIRE_FALSE(f.belts.tryPutItem(QPoint(0, 0), makeItem("iron_ore"), Rotation::East)); REQUIRE_FALSE(f.belts.tryPutItem(QPoint(0, 0), makeItem("iron_ore"), Rotation::East));
REQUIRE(f.bs.isTileOccupied(QPoint(0, 0))); REQUIRE(isTileOccupied(f.state, QPoint(0, 0)));
// Un-queuing re-registers the belt tile so it transports again. // Un-queuing re-registers the belt tile so it transports again.
f.bs.cancelDeconstruction(id); f.bs.cancelDeconstruction(id);
@@ -519,9 +520,9 @@ TEST_CASE("BuildingSystem: second building starts after first completes", "[buil
Tick tick = 0; Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
REQUIRE(bs.getAllSites().size() == 1); REQUIRE(getAllSites(state_bs).size() == 1);
REQUIRE(bs.getAllSites().front().id == id2); REQUIRE(getAllSites(state_bs).front().id == id2);
REQUIRE(bs.getAllSites().front().completesAt > 0); REQUIRE(getAllSites(state_bs).front().completesAt > 0);
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -553,7 +554,7 @@ TEST_CASE("BuildingSystem: miner produces iron_ore after recipe duration", "[bui
static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1, static_cast<int>(secondsToTicks(10.0)) + static_cast<int>(secondsToTicks(1.0)) + 1,
tick); tick);
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// No belt at the output port, so the produced item emerges and stays on the // No belt at the output port, so the produced item emerges and stays on the
// building's virtual output belt (REQ-MAT-OUTPUT-EMERGE). // building's virtual output belt (REQ-MAT-OUTPUT-EMERGE).
@@ -591,7 +592,7 @@ TEST_CASE("BuildingSystem: miner output buffer stalls when full", "[building]")
+ 2 * static_cast<int>(secondsToTicks(1.0)) + 2, + 2 * static_cast<int>(secondsToTicks(1.0)) + 2,
tick); tick);
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// Both produced items are held on the output side (buffer + emerging lane), // Both produced items are held on the output side (buffer + emerging lane),
// which is what the capacity rule counts (REQ-MAT-OUTPUT-EMERGE). // which is what the capacity rule counts (REQ-MAT-OUTPUT-EMERGE).
@@ -624,23 +625,23 @@ TEST_CASE("BuildingSystem: productionBuildingCount excludes construction sites",
Tick tick = 0; Tick tick = 0;
// Both still under construction. // Both still under construction.
REQUIRE(bs.getProductionBuildingCount() == 0); REQUIRE(getProductionBuildingCount(state_bs) == 0);
// The queue builds one at a time: miner (10s) completes at tick 300, then // The queue builds one at a time: miner (10s) completes at tick 300, then
// the smelter (15s) starts and completes at tick 300 + 450 = 750. // the smelter (15s) starts and completes at tick 300 + 450 = 750.
runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
REQUIRE(bs.getProductionBuildingCount() == 1); REQUIRE(getProductionBuildingCount(state_bs) == 1);
runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)), tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(15.0)), tick);
REQUIRE(bs.getProductionBuildingCount() == 2); REQUIRE(getProductionBuildingCount(state_bs) == 2);
// Neither is producing yet: the miner has no recipe selected, and the // Neither is producing yet: the miner has no recipe selected, and the
// smelter (auto-recipe, REQ-BLD-SMELTER) has no input feeding it. // smelter (auto-recipe, REQ-BLD-SMELTER) has no input feeding it.
REQUIRE(bs.getActiveProductionBuildingCount() == 0); REQUIRE(getActiveProductionBuildingCount(state_bs) == 0);
bs.setRecipe(minerId, "mine_iron_ore"); bs.setRecipe(minerId, "mine_iron_ore");
runTicks(bs, belts, 1, tick); runTicks(bs, belts, 1, tick);
REQUIRE(bs.getActiveProductionBuildingCount() == 1); REQUIRE(getActiveProductionBuildingCount(state_bs) == 1);
} }
TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle state", TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle state",
@@ -664,21 +665,21 @@ TEST_CASE("BuildingSystem: activeProductionBuildingCount tracks production cycle
Tick tick = 0; Tick tick = 0;
// Not yet operational while under construction. // Not yet operational while under construction.
REQUIRE(bs.getActiveProductionBuildingCount() == 0); REQUIRE(getActiveProductionBuildingCount(state_bs) == 0);
// Construction completes at tick 300; cycle 1 starts the same tick (completesAt=330). // Construction completes at tick 300; cycle 1 starts the same tick (completesAt=330).
runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
REQUIRE(bs.getActiveProductionBuildingCount() == 1); REQUIRE(getActiveProductionBuildingCount(state_bs) == 1);
// Run cycles 1 and 2 to completion (1s each); cycle 3 stalls once the // Run cycles 1 and 2 to completion (1s each); cycle 3 stalls once the
// output buffer (capacity 2) is full (REQ-MAT-OUTPUT-BUFFER). // output buffer (capacity 2) is full (REQ-MAT-OUTPUT-BUFFER).
runTicks(bs, belts, 2 * static_cast<int>(secondsToTicks(1.0)) + 1, tick); runTicks(bs, belts, 2 * static_cast<int>(secondsToTicks(1.0)) + 1, tick);
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
REQUIRE(b->getOutputItemCount() == 2); REQUIRE(b->getOutputItemCount() == 2);
REQUIRE_FALSE(b->production.has_value()); REQUIRE_FALSE(b->production.has_value());
REQUIRE(bs.getActiveProductionBuildingCount() == 0); REQUIRE(getActiveProductionBuildingCount(state_bs) == 0);
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -719,7 +720,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
bs.tickBeltPull(); bs.tickBeltPull();
const Building* b = bs.findBuilding(sid); const Building* b = findBuilding(state_bs, sid);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// The item was accepted; it may still be travelling inward on the input belt, // The item was accepted; it may still be travelling inward on the input belt,
// so count buffered + in-transit (REQ-MAT-INPUT-INTAKE). // so count buffered + in-transit (REQ-MAT-INPUT-INTAKE).
@@ -754,7 +755,7 @@ TEST_CASE("BuildingSystem: accepted input travels inward before entering the buf
belts.tick(); belts.tick();
bs.tickBeltPull(); // accepts the item onto the input belt at progress 0.0 bs.tickBeltPull(); // accepts the item onto the input belt at progress 0.0
const Building* b = bs.findBuilding(sid); const Building* b = findBuilding(state_bs, sid);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// Reserved but not yet consumable: nothing in the buffer, but it counts against // Reserved but not yet consumable: nothing in the buffer, but it counts against
// the cap via pendingInputCount. // the cap via pendingInputCount.
@@ -802,7 +803,7 @@ TEST_CASE("BuildingSystem: input reservation caps buffered plus in-transit at th
bs.tickBeltPull(); bs.tickBeltPull();
} }
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
const int cap = b->inputBuffer.caps.at(ItemType{"scrap"}); const int cap = b->inputBuffer.caps.at(ItemType{"scrap"});
REQUIRE(cap > 0); REQUIRE(cap > 0);
@@ -848,7 +849,7 @@ TEST_CASE("BuildingSystem: smelter auto-smelts ore without a recipe selection",
// iron_ingot recipe cycle is 2s; run to completion. // iron_ingot recipe cycle is 2s; run to completion.
runTicks(bs, belts, static_cast<int>(secondsToTicks(2.0)) + 2, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(2.0)) + 2, tick);
const Building* b = bs.findBuilding(sid); const Building* b = findBuilding(state_bs, sid);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
bool hasIronIngot = false; bool hasIronIngot = false;
for (const Item& item : outputSideItems(*b)) for (const Item& item : outputSideItems(*b))
@@ -896,7 +897,7 @@ TEST_CASE("BuildingSystem: smelter runs a satisfiable recipe while an incomplete
// copper_ingot cycle is 2.5s; run to completion. // copper_ingot cycle is 2.5s; run to completion.
runTicks(bs, belts, static_cast<int>(secondsToTicks(2.5)) + 2, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(2.5)) + 2, tick);
const Building* b = bs.findBuilding(sid); const Building* b = findBuilding(state_bs, sid);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// Copper was smelted; the lone iron_ore still waits for a second unit. // Copper was smelted; the lone iron_ore still waits for a second unit.
@@ -984,7 +985,7 @@ TEST_CASE("BuildingSystem: output port couples directly into an adjacent input p
// Smelter build (15s) + margin for coupling and a smelt cycle. // Smelter build (15s) + margin for coupling and a smelt cycle.
runTicks(bs, belts, static_cast<int>(secondsToTicks(30.0)), tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(30.0)), tick);
const Building* smelter = bs.findBuilding(smelterId); const Building* smelter = findBuilding(state_bs, smelterId);
REQUIRE(smelter != nullptr); REQUIRE(smelter != nullptr);
// iron_ore reached the smelter over the direct coupling and was smelted. // iron_ore reached the smelter over the direct coupling and was smelted.
bool hasIronIngot = false; bool hasIronIngot = false;
@@ -1024,8 +1025,8 @@ TEST_CASE("BuildingSystem: direct coupling to a non-consumer leaves the item stu
// Both miners build sequentially (10s each), then the producer runs and jams. // Both miners build sequentially (10s each), then the producer runs and jams.
runTicks(bs, belts, static_cast<int>(secondsToTicks(25.0)), tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(25.0)), tick);
const Building* miner = bs.findBuilding(minerId); const Building* miner = findBuilding(state_bs, minerId);
const Building* sink = bs.findBuilding(sinkId); const Building* sink = findBuilding(state_bs, sinkId);
REQUIRE(miner != nullptr); REQUIRE(miner != nullptr);
REQUIRE(sink != nullptr); REQUIRE(sink != nullptr);
// Nothing was delivered, and the producer's output side has backed up to its cap. // Nothing was delivered, and the producer's output side has backed up to its cap.
@@ -1063,14 +1064,14 @@ TEST_CASE("BuildingSystem: setRecipe clears output buffer and active production"
tick); tick);
{ {
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
REQUIRE(b->getOutputItemCount() > 0); REQUIRE(b->getOutputItemCount() > 0);
} }
bs.setRecipe(id, "mine_copper_ore"); bs.setRecipe(id, "mine_copper_ore");
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
// Clearing the output buffer on a recipe change also discards emerging items // Clearing the output buffer on a recipe change also discards emerging items
// (REQ-MAT-OUTPUT-EMERGE). // (REQ-MAT-OUTPUT-EMERGE).
REQUIRE(b->getOutputItemCount() == 0); REQUIRE(b->getOutputItemCount() == 0);
@@ -1106,7 +1107,7 @@ TEST_CASE("BuildingSystem: reprocessing plant output buffer capacity equals max
Tick tick = 0; Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(25.0)) + 1, tick);
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// reprocessing_cycle outputs: 2 iron_ingot (60%), 1 circuit_board (30%), // reprocessing_cycle outputs: 2 iron_ingot (60%), 1 circuit_board (30%),
// 1 advanced_alloy (10%). Max per roll = 2. Capacity = 2 (1× max). // 1 advanced_alloy (10%). Max per roll = 2. Capacity = 2 (1× max).
@@ -1153,7 +1154,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
// Verify all five scrap were accepted; some may still be travelling inward on // Verify all five scrap were accepted; some may still be travelling inward on
// the input belt (REQ-MAT-INPUT-INTAKE), so count buffered + in-transit. // the input belt (REQ-MAT-INPUT-INTAKE), so count buffered + in-transit.
{ {
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
REQUIRE(b->pendingInputCount(ItemType{"scrap"}) == 5); REQUIRE(b->pendingInputCount(ItemType{"scrap"}) == 5);
} }
@@ -1161,7 +1162,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
// Run production cycle (3s = 90 ticks + 1 for the completion tick). // Run production cycle (3s = 90 ticks + 1 for the completion tick).
runTicks(bs, belts, static_cast<int>(secondsToTicks(3.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(3.0)) + 1, tick);
const Building* b = bs.findBuilding(id); const Building* b = findBuilding(state_bs, id);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// Cycle should have completed and output deposited. // Cycle should have completed and output deposited.
REQUIRE_FALSE(b->outputBuffer.items.empty()); REQUIRE_FALSE(b->outputBuffer.items.empty());
@@ -1237,7 +1238,7 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget returns the building id for a
Tick tick = 0; Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
REQUIRE(bs.getAllSites().empty()); REQUIRE(getAllSites(state_bs).empty());
const std::optional<BuildingId> result = const std::optional<BuildingId> result =
bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::South); bs.findRotateInPlaceTarget(BuildingType::Belt, QPoint(0, 0), Rotation::South);
@@ -1367,11 +1368,11 @@ TEST_CASE("BuildingSystem: rotateInPlace updates the rotation field of a constru
rng); rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value(); const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
REQUIRE(bs.findSite(id)->rotation == Rotation::East); REQUIRE(findSite(state_bs, id)->rotation == Rotation::East);
bs.rotateInPlace(id, Rotation::North); bs.rotateInPlace(id, Rotation::North);
REQUIRE(bs.findSite(id)->rotation == Rotation::North); REQUIRE(findSite(state_bs, id)->rotation == Rotation::North);
} }
TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of a queued site", TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of a queued site",
@@ -1391,12 +1392,12 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the construction progress of
rng); rng);
const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value(); const BuildingId id = bs.place(BuildingType::Belt, QPoint(0, 0), Rotation::East, 0).value();
const Tick completesAt = bs.findSite(id)->completesAt; const Tick completesAt = findSite(state_bs, id)->completesAt;
REQUIRE(completesAt > 0); REQUIRE(completesAt > 0);
bs.rotateInPlace(id, Rotation::South); bs.rotateInPlace(id, Rotation::South);
REQUIRE(bs.findSite(id)->completesAt == completesAt); REQUIRE(findSite(state_bs, id)->completesAt == completesAt);
} }
TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direction on an operational building", TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direction on an operational building",
@@ -1419,14 +1420,14 @@ TEST_CASE("BuildingSystem: rotateInPlace updates rotation and output port direct
Tick tick = 0; Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick); runTicks(bs, belts, static_cast<int>(secondsToTicks(1.0)) + 1, tick);
REQUIRE(bs.findBuilding(id) != nullptr); REQUIRE(findBuilding(state_bs, id) != nullptr);
const Building& before = *bs.findBuilding(id); const Building& before = *findBuilding(state_bs, id);
REQUIRE(before.outputPorts[0].direction == Rotation::East); REQUIRE(before.outputPorts[0].direction == Rotation::East);
bs.rotateInPlace(id, Rotation::North); bs.rotateInPlace(id, Rotation::North);
const Building& after = *bs.findBuilding(id); const Building& after = *findBuilding(state_bs, id);
REQUIRE(after.rotation == Rotation::North); REQUIRE(after.rotation == Rotation::North);
REQUIRE(after.outputPorts[0].direction == Rotation::North); REQUIRE(after.outputPorts[0].direction == Rotation::North);
} }
@@ -1468,11 +1469,11 @@ TEST_CASE("BuildingSystem: rotateInPlace preserves the output filters of a split
// Run until construction completes, so the splitter is registered with BeltSystem. // Run until construction completes, so the splitter is registered with BeltSystem.
Tick tick = 0; Tick tick = 0;
while (f.bs.getAllBuildings().empty() && tick < 100000) while (getAllBuildings(f.state).empty() && tick < 100000)
{ {
runTicks(f.bs, f.belts, 1, tick); runTicks(f.bs, f.belts, 1, tick);
} }
REQUIRE(f.bs.getAllBuildings().size() == 1); REQUIRE(getAllBuildings(f.state).size() == 1);
const std::vector<ItemType> filterA{ ItemType{"iron_ore"} }; const std::vector<ItemType> filterA{ ItemType{"iron_ore"} };
const std::vector<ItemType> filterB{ ItemType{"copper_ore"} }; const std::vector<ItemType> filterB{ ItemType{"copper_ore"} };
@@ -1496,7 +1497,7 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca
const QPoint tile(5, 5); const QPoint tile(5, 5);
const BuildingId id = f.bs.place(BuildingType::Splitter, tile, Rotation::East, 0).value(); const BuildingId id = f.bs.place(BuildingType::Splitter, tile, Rotation::East, 0).value();
REQUIRE(id != kInvalidBuildingId); REQUIRE(id != kInvalidBuildingId);
REQUIRE(f.bs.findSite(id) != nullptr); REQUIRE(findSite(f.state, id) != nullptr);
// Configure an output filter on the still-queued splitter site. // Configure an output filter on the still-queued splitter site.
const std::vector<ItemType> filterA{ ItemType{"iron_ore"} }; const std::vector<ItemType> filterA{ ItemType{"iron_ore"} };
@@ -1513,12 +1514,12 @@ TEST_CASE("BuildingSystem: splitter filters configured on a construction site ca
// Run until construction completes. // Run until construction completes.
Tick tick = 0; Tick tick = 0;
while (f.bs.getAllBuildings().empty() && tick < 100000) while (getAllBuildings(f.state).empty() && tick < 100000)
{ {
runTicks(f.bs, f.belts, 1, tick); runTicks(f.bs, f.belts, 1, tick);
} }
REQUIRE(f.bs.getAllBuildings().size() == 1); REQUIRE(getAllBuildings(f.state).size() == 1);
REQUIRE(f.bs.getAllBuildings()[0].type == BuildingType::Splitter); REQUIRE(getAllBuildings(f.state)[0].type == BuildingType::Splitter);
// The built splitter is registered with BeltSystem carrying the filters. // The built splitter is registered with BeltSystem carrying the filters.
const std::optional<BeltSystem::SplitterInfo> builtInfo = f.belts.getSplitterInfo(tile); const std::optional<BeltSystem::SplitterInfo> builtInfo = f.belts.getSplitterInfo(tile);
@@ -1669,10 +1670,10 @@ namespace
// Advances the sim until the given site becomes an operational building, or a // Advances the sim until the given site becomes an operational building, or a
// safety cap is reached. // safety cap is reached.
void buildToCompletion(BuildingSystem& bs, BeltSystem& belts, BuildingId id, void buildToCompletion(BuildingSystem& bs, const FactoryState& state,
Tick& tick) BeltSystem& belts, BuildingId id, Tick& tick)
{ {
for (int i = 0; i < 20000 && bs.findBuilding(id) == nullptr; ++i) for (int i = 0; i < 20000 && findBuilding(state, id) == nullptr; ++i)
{ {
runTicks(bs, belts, 1, tick); runTicks(bs, belts, 1, tick);
} }
@@ -1709,8 +1710,8 @@ TEST_CASE("BuildingSystem: getInputPorts matches between a site and the built bu
const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value(); const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0).value();
const std::vector<Port> sitePorts = f.bs.getInputPorts(id); const std::vector<Port> sitePorts = f.bs.getInputPorts(id);
buildToCompletion(f.bs, f.belts, id, tick); buildToCompletion(f.bs, f.state, f.belts, id, tick);
REQUIRE(f.bs.findBuilding(id) != nullptr); REQUIRE(findBuilding(f.state, id) != nullptr);
const std::vector<Port> builtPorts = f.bs.getInputPorts(id); const std::vector<Port> builtPorts = f.bs.getInputPorts(id);
// The operational path (stored inputPorts) agrees with the site path (mask-derived). // The operational path (stored inputPorts) agrees with the site path (mask-derived).
@@ -1725,7 +1726,7 @@ TEST_CASE("BuildingSystem: getInputPorts invariants hold for a rotated site", "[
{ {
PlacementFixture f; PlacementFixture f;
const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::South, 0).value(); const BuildingId id = f.bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::South, 0).value();
const ConstructionSite* site = f.bs.findSite(id); const ConstructionSite* site = findSite(f.state, id);
REQUIRE(site != nullptr); REQUIRE(site != nullptr);
std::set<std::pair<int, int>> bodySet; std::set<std::pair<int, int>> bodySet;

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include "Building.h" #include "Building.h"
#include "BuildingSystem.h" #include "BuildingSystem.h"
@@ -218,7 +219,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
const Building* b = sim.getBuildings().findBuilding(yardId); const Building* b = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b != nullptr); REQUIRE(b != nullptr);
// armor_plate needs 2 iron_ingot; interceptor needs 3 iron_ingot + 1 circuit_board // armor_plate needs 2 iron_ingot; interceptor needs 3 iron_ingot + 1 circuit_board
// Total iron_ingot = 5, buffer cap = 2 * 5 = 10 // Total iron_ingot = 5, buffer cap = 2 * 5 = 10
@@ -243,7 +244,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
fillMaterials(sim, yardId, *def, emptyLayout); fillMaterials(sim, yardId, *def, emptyLayout);
sim.tick(); sim.tick();
const Building* b1 = sim.getBuildings().findBuilding(yardId); const Building* b1 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b1 != nullptr); REQUIRE(b1 != nullptr);
REQUIRE(b1->production.has_value()); REQUIRE(b1->production.has_value());
@@ -257,7 +258,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
const Building* b2 = sim.getBuildings().findBuilding(yardId); const Building* b2 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b2 != nullptr); REQUIRE(b2 != nullptr);
CHECK_FALSE(b2->production.has_value()); CHECK_FALSE(b2->production.has_value());
} }
@@ -323,13 +324,13 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
layout.placedModules.push_back(pm); layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
const Building* b1 = sim.getBuildings().findBuilding(yardId); const Building* b1 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b1 != nullptr); REQUIRE(b1 != nullptr);
REQUIRE(b1->shipLayout.has_value()); REQUIRE(b1->shipLayout.has_value());
SimulationTestAccess::buildings(sim).setRecipe(yardId,"destroyer"); SimulationTestAccess::buildings(sim).setRecipe(yardId,"destroyer");
const Building* b2 = sim.getBuildings().findBuilding(yardId); const Building* b2 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b2 != nullptr); REQUIRE(b2 != nullptr);
CHECK_FALSE(b2->shipLayout.has_value()); CHECK_FALSE(b2->shipLayout.has_value());
} }
@@ -352,14 +353,14 @@ TEST_CASE("Shipyard: setRecipe with unchanged recipe keeps ship layout",
layout.placedModules.push_back(pm); layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout); SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
const Building* b1 = sim.getBuildings().findBuilding(yardId); const Building* b1 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b1 != nullptr); REQUIRE(b1 != nullptr);
REQUIRE(b1->shipLayout.has_value()); REQUIRE(b1->shipLayout.has_value());
// Re-selecting the same recipe must be a no-op and preserve the layout. // Re-selecting the same recipe must be a no-op and preserve the layout.
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor"); SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
const Building* b2 = sim.getBuildings().findBuilding(yardId); const Building* b2 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b2 != nullptr); REQUIRE(b2 != nullptr);
REQUIRE(b2->shipLayout.has_value()); REQUIRE(b2->shipLayout.has_value());
REQUIRE(b2->shipLayout->placedModules.size() == 1); REQUIRE(b2->shipLayout->placedModules.size() == 1);

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
@@ -214,7 +215,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya
// Verify the shipyard production field cleared (i.e. the cycle completed // Verify the shipyard production field cleared (i.e. the cycle completed
// and is not still running). // and is not still running).
bool productionCleared = false; bool productionCleared = false;
for (const Building& b : sim.getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(sim.getFactoryState()))
{ {
if (b.id == yardId) if (b.id == yardId)
{ {

View File

@@ -1,4 +1,5 @@
#include "catch.hpp" #include "catch.hpp"
#include "FactoryQueries.h"
#include <random> #include <random>
@@ -100,7 +101,7 @@ TEST_CASE("WaveSystem: Simulation pre-places HQ + 2 player + 2 enemy stations",
// HQ is still a Building (for belt integration). // HQ is still a Building (for belt integration).
int hqCount = 0; int hqCount = 0;
for (const Building& b : sim.getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(sim.getFactoryState()))
{ {
if (b.type == BuildingType::Hq) { ++hqCount; } if (b.type == BuildingType::Hq) { ++hqCount; }
} }
@@ -143,7 +144,7 @@ TEST_CASE("WaveSystem: HQ anchor is at asteroid right edge", "[wave]")
{ {
const Simulation sim(loadTestConfig(), 42); const Simulation sim(loadTestConfig(), 42);
for (const Building& b : sim.getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(sim.getFactoryState()))
{ {
if (b.type != BuildingType::Hq) { continue; } if (b.type != BuildingType::Hq) { continue; }
// Rightmost body cell must be at x = -1 (asteroid right edge). // Rightmost body cell must be at x = -1 (asteroid right edge).

View File

@@ -1,4 +1,5 @@
#include "GameWorldView.h" #include "GameWorldView.h"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@@ -577,7 +578,7 @@ QRect GameWorldView::getViewportRect() const
float GameWorldView::getAsteroidLeftEdge() const float GameWorldView::getAsteroidLeftEdge() const
{ {
float leftX = -static_cast<float>(m_sim->getCurrentAsteroidWidth_tiles()); float leftX = -static_cast<float>(m_sim->getCurrentAsteroidWidth_tiles());
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
for (const QPoint& cell : b.bodyCells) for (const QPoint& cell : b.bodyCells)
{ {
@@ -672,7 +673,7 @@ bool GameWorldView::isValidPlacement(BuildingType type, QPoint anchor,
bool anyOccupied = false; bool anyOccupied = false;
for (const QPoint& relCell : parsed.bodyCells) for (const QPoint& relCell : parsed.bodyCells)
{ {
if (m_sim->getBuildings().isTileOccupied(anchor + relCell)) if (isTileOccupied(m_sim->getFactoryState(), anchor + relCell))
{ {
anyOccupied = true; anyOccupied = true;
break; break;
@@ -688,7 +689,7 @@ bool GameWorldView::isValidPlacement(BuildingType type, QPoint anchor,
std::optional<BuildingId> GameWorldView::buildingAtTile(QPoint tile) const std::optional<BuildingId> GameWorldView::buildingAtTile(QPoint tile) const
{ {
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
for (const QPoint& cell : b.bodyCells) for (const QPoint& cell : b.bodyCells)
{ {
@@ -703,7 +704,7 @@ std::optional<BuildingId> GameWorldView::buildingAtTile(QPoint tile) const
std::optional<BuildingId> GameWorldView::siteAtTile(QPoint tile) const std::optional<BuildingId> GameWorldView::siteAtTile(QPoint tile) const
{ {
for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState()))
{ {
for (const QPoint& cell : s.bodyCells) for (const QPoint& cell : s.bodyCells)
{ {
@@ -725,7 +726,7 @@ std::vector<BuildingId> GameWorldView::buildingsInBox(QPoint cornerA, QPoint cor
const int y1 = std::max(cornerA.y(), cornerB.y()); const int y1 = std::max(cornerA.y(), cornerB.y());
std::vector<BuildingId> ids; std::vector<BuildingId> ids;
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
for (const QPoint& cell : b.bodyCells) for (const QPoint& cell : b.bodyCells)
{ {
@@ -737,7 +738,7 @@ std::vector<BuildingId> GameWorldView::buildingsInBox(QPoint cornerA, QPoint cor
} }
} }
} }
for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState()))
{ {
for (const QPoint& cell : s.bodyCells) for (const QPoint& cell : s.bodyCells)
{ {
@@ -951,14 +952,14 @@ TunnelTileMap GameWorldView::collectTunnelTiles() const
// single-cell tile, so a just-placed tunnel (not yet constructed) is matchable // single-cell tile, so a just-placed tunnel (not yet constructed) is matchable
// (REQ-BLD-TUNNEL-MODE, REQ-BLD-TUNNEL-SELECT-HIGHLIGHT). // (REQ-BLD-TUNNEL-MODE, REQ-BLD-TUNNEL-SELECT-HIGHLIGHT).
TunnelTileMap tunnels; TunnelTileMap tunnels;
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
if (b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit) if (b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit)
{ {
tunnels[b.anchor] = TunnelTileInfo{b.type, b.rotation}; tunnels[b.anchor] = TunnelTileInfo{b.type, b.rotation};
} }
} }
for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState()))
{ {
if (s.type == BuildingType::TunnelEntry || s.type == BuildingType::TunnelExit) if (s.type == BuildingType::TunnelEntry || s.type == BuildingType::TunnelExit)
{ {
@@ -1034,7 +1035,7 @@ void GameWorldView::placeAtTile(QPoint tile)
|| type == BuildingType::TunnelEntry || type == BuildingType::TunnelEntry
|| type == BuildingType::TunnelExit) || type == BuildingType::TunnelExit)
{ {
if (!m_sim->getBuildings().isTileOccupied(tile) && canAfford(type)) if (!isTileOccupied(m_sim->getFactoryState(), tile) && canAfford(type))
{ {
enqueuePlaceBuilding(type, tile, m_ghostRotation); enqueuePlaceBuilding(type, tile, m_ghostRotation);
} }
@@ -1061,7 +1062,7 @@ void GameWorldView::recomputeBeltDragPath(QPoint cursorTile)
std::optional<BuildingType> targetType; std::optional<BuildingType> targetType;
if (targetId.has_value()) if (targetId.has_value())
{ {
if (const Building* building = m_sim->getBuildings().findBuilding(*targetId)) if (const Building* building = findBuilding(m_sim->getFactoryState(), *targetId))
{ {
targetType = building->type; targetType = building->type;
} }
@@ -1069,7 +1070,7 @@ void GameWorldView::recomputeBeltDragPath(QPoint cursorTile)
else if (std::optional<BuildingId> siteId = siteAtTile(cursorTile); siteId.has_value()) else if (std::optional<BuildingId> siteId = siteAtTile(cursorTile); siteId.has_value())
{ {
targetId = siteId; targetId = siteId;
if (const ConstructionSite* site = m_sim->getBuildings().findSite(*siteId)) if (const ConstructionSite* site = findSite(m_sim->getFactoryState(), *siteId))
{ {
targetType = site->type; targetType = site->type;
} }
@@ -1298,7 +1299,7 @@ bool GameWorldView::drawBuildingIcon(QPainter& painter, BuildingType type,
void GameWorldView::drawBuildings(QPainter& painter) void GameWorldView::drawBuildings(QPainter& painter)
{ {
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
const std::map<BuildingType, BuildingVisuals>::const_iterator it = const std::map<BuildingType, BuildingVisuals>::const_iterator it =
m_visuals->buildings.find(b.type); m_visuals->buildings.find(b.type);
@@ -1365,7 +1366,7 @@ void GameWorldView::drawBuildings(QPainter& painter)
} }
painter.setOpacity(0.5); painter.setOpacity(0.5);
for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState()))
{ {
const std::map<BuildingType, BuildingVisuals>::const_iterator it = const std::map<BuildingType, BuildingVisuals>::const_iterator it =
m_visuals->buildings.find(s.type); m_visuals->buildings.find(s.type);
@@ -1442,7 +1443,7 @@ void GameWorldView::drawBuildings(QPainter& painter)
// after every building and construction site fill so a belt (or other tile) // after every building and construction site fill so a belt (or other tile)
// placed directly below the HQ cannot overpaint the bar (REQ-UI-STATUS-LIGHT // placed directly below the HQ cannot overpaint the bar (REQ-UI-STATUS-LIGHT
// neighbours case, same rationale as the selection highlights below). // neighbours case, same rationale as the selection highlights below).
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
if (b.type != BuildingType::Hq) { continue; } if (b.type != BuildingType::Hq) { continue; }
const QPointF tl = tileToWidget(b.anchor); const QPointF tl = tileToWidget(b.anchor);
@@ -1473,12 +1474,12 @@ std::optional<QRectF> GameWorldView::footprintWidgetRect(BuildingId id) const
std::optional<QPoint> anchor; std::optional<QPoint> anchor;
std::optional<QSize> footprint; std::optional<QSize> footprint;
if (const Building* b = m_sim->getBuildings().findBuilding(id)) if (const Building* b = findBuilding(m_sim->getFactoryState(), id))
{ {
anchor = b->anchor; anchor = b->anchor;
footprint = b->footprint; footprint = b->footprint;
} }
else if (const ConstructionSite* s = m_sim->getBuildings().findSite(id)) else if (const ConstructionSite* s = findSite(m_sim->getFactoryState(), id))
{ {
anchor = s->anchor; anchor = s->anchor;
footprint = s->footprint; footprint = s->footprint;
@@ -1530,13 +1531,13 @@ void GameWorldView::drawCopyConfigFeedback(QPainter& painter)
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
painter.setBrush(color); painter.setBrush(color);
const BuildingType type = m_copiedConfig->type; const BuildingType type = m_copiedConfig->type;
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
if (b.type != type) { continue; } if (b.type != type) { continue; }
const std::optional<QRectF> rect = footprintWidgetRect(b.id); const std::optional<QRectF> rect = footprintWidgetRect(b.id);
if (rect.has_value()) { painter.drawRect(*rect); } if (rect.has_value()) { painter.drawRect(*rect); }
} }
for (const ConstructionSite& s : m_sim->getBuildings().getAllSites()) for (const ConstructionSite& s : getAllSites(m_sim->getFactoryState()))
{ {
if (s.type != type) { continue; } if (s.type != type) { continue; }
const std::optional<QRectF> rect = footprintWidgetRect(s.id); const std::optional<QRectF> rect = footprintWidgetRect(s.id);
@@ -1571,7 +1572,7 @@ void GameWorldView::drawPortItems(QPainter& painter)
const double margin = kPortMarginTiles * static_cast<double>(getTilePx()); const double margin = kPortMarginTiles * static_cast<double>(getTilePx());
QRegion clip(rect()); QRegion clip(rect());
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
if (b.type == BuildingType::Belt || b.type == BuildingType::Splitter if (b.type == BuildingType::Belt || b.type == BuildingType::Splitter
|| b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit) || b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit)
@@ -1982,11 +1983,11 @@ void GameWorldView::drawSelectedTunnelConnections(QPainter& painter)
std::optional<QPoint> anchor; std::optional<QPoint> anchor;
std::optional<BuildingType> type; std::optional<BuildingType> type;
Rotation rotation = Rotation::East; Rotation rotation = Rotation::East;
if (const Building* b = m_sim->getBuildings().findBuilding(id)) if (const Building* b = findBuilding(m_sim->getFactoryState(), id))
{ {
anchor = b->anchor; type = b->type; rotation = b->rotation; anchor = b->anchor; type = b->type; rotation = b->rotation;
} }
else if (const ConstructionSite* s = m_sim->getBuildings().findSite(id)) else if (const ConstructionSite* s = findSite(m_sim->getFactoryState(), id))
{ {
anchor = s->anchor; type = s->type; rotation = s->rotation; anchor = s->anchor; type = s->type; rotation = s->rotation;
} }
@@ -2091,7 +2092,7 @@ void GameWorldView::drawOverlays(QPainter& painter)
// Queued for deconstruction: tint every building currently in the // Queued for deconstruction: tint every building currently in the
// deconstruction queue, regardless of mode (REQ-BLD-DECON-QUEUE). // deconstruction queue, regardless of mode (REQ-BLD-DECON-QUEUE).
for (const Building& b : m_sim->getBuildings().getAllBuildings()) for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
{ {
if (!b.queuedForDeconstruction) { continue; } if (!b.queuedForDeconstruction) { continue; }
for (const QPoint& cell : b.bodyCells) for (const QPoint& cell : b.bodyCells)
@@ -2106,12 +2107,12 @@ void GameWorldView::drawOverlays(QPainter& painter)
{ {
for (BuildingId id : buildingsInBox(m_boxStartTile, m_boxCurrentTile)) for (BuildingId id : buildingsInBox(m_boxStartTile, m_boxCurrentTile))
{ {
const Building* b = m_sim->getBuildings().findBuilding(id); const Building* b = findBuilding(m_sim->getFactoryState(), id);
if (b && b->type == BuildingType::Hq) { continue; } if (b && b->type == BuildingType::Hq) { continue; }
const std::vector<QPoint>* cells = nullptr; const std::vector<QPoint>* cells = nullptr;
const ConstructionSite* s = nullptr; const ConstructionSite* s = nullptr;
if (b) { cells = &b->bodyCells; } if (b) { cells = &b->bodyCells; }
else if ((s = m_sim->getBuildings().findSite(id))) { cells = &s->bodyCells; } else if ((s = findSite(m_sim->getFactoryState(), id))) { cells = &s->bodyCells; }
if (cells) if (cells)
{ {
for (const QPoint& cell : *cells) for (const QPoint& cell : *cells)
@@ -2123,7 +2124,7 @@ void GameWorldView::drawOverlays(QPainter& painter)
} }
else if (m_deconstructMode && m_deconstructHoverBuildingId.has_value()) else if (m_deconstructMode && m_deconstructHoverBuildingId.has_value())
{ {
const Building* b = m_sim->getBuildings().findBuilding(*m_deconstructHoverBuildingId); const Building* b = findBuilding(m_sim->getFactoryState(), *m_deconstructHoverBuildingId);
if (b) if (b)
{ {
for (const QPoint& cell : b->bodyCells) for (const QPoint& cell : b->bodyCells)
@@ -2748,7 +2749,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
if (m_deconstructMode) if (m_deconstructMode)
{ {
const BuildingSystem& buildings = m_sim->getBuildings(); const FactoryState& factory = m_sim->getFactoryState();
// Split covered ids into construction sites (removed instantly) and // Split covered ids into construction sites (removed instantly) and
// operational deconstructible buildings (the HQ is protected; player // operational deconstructible buildings (the HQ is protected; player
@@ -2757,12 +2758,12 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
std::vector<BuildingId> operational; std::vector<BuildingId> operational;
for (BuildingId id : boxIds) for (BuildingId id : boxIds)
{ {
if (const Building* b = buildings.findBuilding(id)) if (const Building* b = findBuilding(factory, id))
{ {
if (b->type == BuildingType::Hq) { continue; } if (b->type == BuildingType::Hq) { continue; }
operational.push_back(id); operational.push_back(id);
} }
else if (buildings.findSite(id)) else if (findSite(factory, id))
{ {
sites.push_back(id); sites.push_back(id);
} }
@@ -2784,7 +2785,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
bool allQueued = !operational.empty(); bool allQueued = !operational.empty();
for (BuildingId id : operational) for (BuildingId id : operational)
{ {
if (!buildings.isQueuedForDeconstruction(id)) { allQueued = false; break; } if (!isQueuedForDeconstruction(factory, id)) { allQueued = false; break; }
} }
for (BuildingId id : operational) for (BuildingId id : operational)
{ {
@@ -2795,7 +2796,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
command->id = id; command->id = id;
enqueueCommand(command); enqueueCommand(command);
} }
else if (!buildings.isQueuedForDeconstruction(id)) else if (!isQueuedForDeconstruction(factory, id))
{ {
std::shared_ptr<DeconstructCommand> command = std::shared_ptr<DeconstructCommand> command =
std::make_shared<DeconstructCommand>(); std::make_shared<DeconstructCommand>();
@@ -2997,7 +2998,7 @@ void GameWorldView::pasteConfigTo(BuildingId id)
{ {
// Operational splitters are configured by tile; sites by BuildingId // Operational splitters are configured by tile; sites by BuildingId
// (mirrors SelectedBuildingPanel::onSplitterFilterChanged). // (mirrors SelectedBuildingPanel::onSplitterFilterChanged).
if (const Building* building = m_sim->getBuildings().findBuilding(id)) if (const Building* building = findBuilding(m_sim->getFactoryState(), id))
{ {
std::shared_ptr<SetSplitterFiltersCommand> command = std::shared_ptr<SetSplitterFiltersCommand> command =
std::make_shared<SetSplitterFiltersCommand>(); std::make_shared<SetSplitterFiltersCommand>();

View File

@@ -1,4 +1,5 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "FactoryQueries.h"
#include <map> #include <map>
#include <random> #include <random>
@@ -277,9 +278,9 @@ void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> e
{ {
// A construction site has no Building yet; fall back to its site record so // A construction site has no Building yet; fall back to its site record so
// the shipyard layout can be configured before it is built (REQ-BLD-SITE-CONFIG). // the shipyard layout can be configured before it is built (REQ-BLD-SITE-CONFIG).
const Building* b = m_sim->getBuildings().findBuilding(event->shipyardId); const Building* b = findBuilding(m_sim->getFactoryState(), event->shipyardId);
const ConstructionSite* s = const ConstructionSite* s =
b ? nullptr : m_sim->getBuildings().findSite(event->shipyardId); b ? nullptr : findSite(m_sim->getFactoryState(), event->shipyardId);
if (!b && !s) if (!b && !s)
{ {
return; return;
@@ -304,9 +305,9 @@ void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent
// A construction site has no Building yet; fall back to its site record so // A construction site has no Building yet; fall back to its site record so
// the recipe/schematic can be chosen before it is built (REQ-BLD-SITE-CONFIG). // the recipe/schematic can be chosen before it is built (REQ-BLD-SITE-CONFIG).
const Building* b = m_sim->getBuildings().findBuilding(event->buildingId); const Building* b = findBuilding(m_sim->getFactoryState(), event->buildingId);
const ConstructionSite* s = const ConstructionSite* s =
b ? nullptr : m_sim->getBuildings().findSite(event->buildingId); b ? nullptr : findSite(m_sim->getFactoryState(), event->buildingId);
if (!b && !s) if (!b && !s)
{ {
return; return;

View File

@@ -1,4 +1,5 @@
#include "SelectedBuildingPanel.h" #include "SelectedBuildingPanel.h"
#include "FactoryQueries.h"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@@ -234,8 +235,8 @@ void SelectedBuildingPanel::buildSingle(BuildingId id)
m_singleBuildingId = id; m_singleBuildingId = id;
hideAllWidgets(); hideAllWidgets();
const Building* b = m_sim->getBuildings().findBuilding(id); const Building* b = findBuilding(m_sim->getFactoryState(), id);
const ConstructionSite* s = b ? nullptr : m_sim->getBuildings().findSite(id); const ConstructionSite* s = b ? nullptr : findSite(m_sim->getFactoryState(), id);
if (!b && !s) if (!b && !s)
{ {
buildEmpty(); buildEmpty();
@@ -602,7 +603,7 @@ void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason)
// category owns the panel there is none: yieldToFieldSelection() has cleared it, so // category owns the panel there is none: yieldToFieldSelection() has cleared it, so
// this returns immediately and the field panel refreshes itself off the same events. // this returns immediately and the field panel refreshes itself off the same events.
if (!m_singleBuildingId.has_value()) { return; } if (!m_singleBuildingId.has_value()) { return; }
const Building* b = m_sim->getBuildings().findBuilding(*m_singleBuildingId); const Building* b = findBuilding(m_sim->getFactoryState(), *m_singleBuildingId);
if (b) if (b)
{ {
if (m_titleLabel->text().startsWith(tr("(Building) "))) if (m_titleLabel->text().startsWith(tr("(Building) ")))
@@ -615,7 +616,7 @@ void SelectedBuildingPanel::refreshSelectionDisplay(RefreshReason reason)
} }
return; return;
} }
const ConstructionSite* s = m_sim->getBuildings().findSite(*m_singleBuildingId); const ConstructionSite* s = findSite(m_sim->getFactoryState(), *m_singleBuildingId);
if (s) if (s)
{ {
// A periodic tick only advances construction progress, so update just the // A periodic tick only advances construction progress, so update just the
@@ -649,13 +650,13 @@ void SelectedBuildingPanel::buildMulti(const std::vector<BuildingId>& ids)
std::map<BuildingType, int> counts; std::map<BuildingType, int> counts;
for (BuildingId id : ids) for (BuildingId id : ids)
{ {
const Building* b = m_sim->getBuildings().findBuilding(id); const Building* b = findBuilding(m_sim->getFactoryState(), id);
if (b) if (b)
{ {
counts[b->type]++; counts[b->type]++;
continue; continue;
} }
const ConstructionSite* s = m_sim->getBuildings().findSite(id); const ConstructionSite* s = findSite(m_sim->getFactoryState(), id);
if (s) if (s)
{ {
counts[s->type]++; counts[s->type]++;
@@ -817,7 +818,7 @@ void SelectedBuildingPanel::onClearBelt()
std::vector<QPoint> tiles; std::vector<QPoint> tiles;
for (BuildingId id : m_selectedBuildingIds) for (BuildingId id : m_selectedBuildingIds)
{ {
const Building* b = m_sim->getBuildings().findBuilding(id); const Building* b = findBuilding(m_sim->getFactoryState(), id);
if (b && isBeltSubsystemType(b->type)) if (b && isBeltSubsystemType(b->type))
{ {
for (const QPoint& cell : b->bodyCells) for (const QPoint& cell : b->bodyCells)