migrate every factory query off BuildingSystem onto the free functions

This commit is contained in:
2026-08-05 06:45:49 +02:00
parent 1fb63cce4e
commit 58b94223f7
20 changed files with 212 additions and 244 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,
@@ -159,7 +152,6 @@ public:
// Producing while its output buffer holds scrap, Starved when empty. // Producing while its output buffer holds scrap, Starved when empty.
std::optional<ProductionStatus> getProductionStatus(const Building& building) const; std::optional<ProductionStatus> getProductionStatus(const Building& building) const;
std::vector<BeltTileInfo> getAllBeltTiles() const; std::vector<BeltTileInfo> getAllBeltTiles() const;
bool isTileOccupied(QPoint tile) const;
// Visits every item currently emerging from a building output port on its // Visits every item currently emerging from a building output port on its
// virtual output belt (REQ-MAT-OUTPUT-EMERGE), passing the item type and its // virtual output belt (REQ-MAT-OUTPUT-EMERGE), passing the item type and its
@@ -186,8 +178,6 @@ public:
// currently on the tile are discarded by BeltSystem::removeTile). // currently on the tile are discarded by BeltSystem::removeTile).
void rotateInPlace(BuildingId id, Rotation newRotation); void rotateInPlace(BuildingId id, Rotation newRotation);
// Find nearest operational building of the given type; nullptr if none.
const Building* findNearestBuilding(QVector2D worldPos, BuildingType type) const;
// Input-capable adjacent tiles for a building or construction site // Input-capable adjacent tiles for a building or construction site
// (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS): each returned Port.tile is the // (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS): each returned Port.tile is the
@@ -201,7 +191,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 +223,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)