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
169 lines
5.4 KiB
C++
169 lines
5.4 KiB
C++
#include "BuildingConfig.h"
|
|
#include "FactoryQueries.h"
|
|
|
|
#include <algorithm>
|
|
#include <climits>
|
|
|
|
#include "BeltSystem.h"
|
|
#include "Building.h"
|
|
#include "BuildingSystem.h"
|
|
#include "Simulation.h"
|
|
|
|
namespace
|
|
{
|
|
// The blueprint-relevant geometry shared by operational buildings and construction
|
|
// sites. Resolved from whichever of the two a selected id refers to.
|
|
struct SelectedBuilding
|
|
{
|
|
BuildingId id;
|
|
BuildingType type;
|
|
Rotation rotation;
|
|
QPoint anchor;
|
|
const std::vector<QPoint>* bodyCells;
|
|
};
|
|
|
|
// Resolves a selected id to a player-placeable building or construction site, if it
|
|
// is one. Returns std::nullopt for an unknown id or a non-player-placeable building
|
|
// (the HQ and defence stations, per REQ-UI-BLUEPRINT-CREATE).
|
|
std::optional<SelectedBuilding> resolvePlaceable(const Simulation& sim, BuildingId id)
|
|
{
|
|
const Building* building = findBuilding(sim.getFactoryState(), id);
|
|
const ConstructionSite* site = building ? nullptr : findSite(sim.getFactoryState(), id);
|
|
if (!building && !site)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
const BuildingType type = building ? building->type : site->type;
|
|
const BuildingDef* def = sim.getConfig().buildings.findBuildingDef(type);
|
|
if (!def || !def->playerPlaceable)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
SelectedBuilding resolved;
|
|
resolved.id = id;
|
|
resolved.type = type;
|
|
resolved.rotation = building ? building->rotation : site->rotation;
|
|
resolved.anchor = building ? building->anchor : site->anchor;
|
|
resolved.bodyCells = building ? &building->bodyCells : &site->bodyCells;
|
|
return resolved;
|
|
}
|
|
} // namespace
|
|
|
|
std::optional<BuildingConfig> readBuildingConfig(const Simulation& sim, BuildingId id)
|
|
{
|
|
const Building* building = findBuilding(sim.getFactoryState(), id);
|
|
const ConstructionSite* site = building ? nullptr : findSite(sim.getFactoryState(), id);
|
|
if (!building && !site)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
BuildingConfig config;
|
|
config.type = building ? building->type : site->type;
|
|
|
|
const std::string& recipeId = building ? building->recipeId : site->recipeId;
|
|
if (!recipeId.empty())
|
|
{
|
|
config.recipeId = recipeId;
|
|
}
|
|
|
|
config.shipLayout = building ? building->shipLayout : site->shipLayout;
|
|
|
|
if (config.type == BuildingType::Splitter)
|
|
{
|
|
config.isSplitter = true;
|
|
if (building)
|
|
{
|
|
// Operational splitter filters live in the BeltSystem, keyed by tile.
|
|
const std::optional<BeltSystem::SplitterInfo> info =
|
|
sim.getBelts().getSplitterInfo(building->anchor);
|
|
if (info.has_value())
|
|
{
|
|
config.splitterFilterA = info->filterA;
|
|
config.splitterFilterB = info->filterB;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A site keeps its pre-completion filters on the ConstructionSite.
|
|
config.splitterFilterA = site->splitterFilterA;
|
|
config.splitterFilterB = site->splitterFilterB;
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
Blueprint captureBlueprintFromSelection(const Simulation& sim,
|
|
const std::vector<BuildingId>& selectedIds)
|
|
{
|
|
std::vector<SelectedBuilding> entries;
|
|
entries.reserve(selectedIds.size());
|
|
for (const BuildingId id : selectedIds)
|
|
{
|
|
const std::optional<SelectedBuilding> resolved = resolvePlaceable(sim, id);
|
|
if (resolved.has_value())
|
|
{
|
|
entries.push_back(*resolved);
|
|
}
|
|
}
|
|
|
|
if (entries.empty())
|
|
{
|
|
return Blueprint{};
|
|
}
|
|
|
|
int minX = INT_MAX, maxX = INT_MIN;
|
|
int minY = INT_MAX, maxY = INT_MIN;
|
|
for (const SelectedBuilding& e : entries)
|
|
{
|
|
for (const QPoint& cell : *e.bodyCells)
|
|
{
|
|
minX = std::min(minX, cell.x());
|
|
maxX = std::max(maxX, cell.x());
|
|
minY = std::min(minY, cell.y());
|
|
maxY = std::max(maxY, cell.y());
|
|
}
|
|
}
|
|
|
|
const QPoint center((minX + maxX) / 2, (minY + maxY) / 2);
|
|
|
|
Blueprint blueprint;
|
|
blueprint.buildings.reserve(entries.size());
|
|
for (const SelectedBuilding& e : entries)
|
|
{
|
|
BlueprintBuilding building;
|
|
building.type = e.type;
|
|
building.rotation = e.rotation;
|
|
building.offset = e.anchor - center;
|
|
// Recipe / schematic / layout / splitter-filter capture is shared with the
|
|
// copy-settings gesture (REQ-BLD-COPY-CONFIG) via readBuildingConfig, which
|
|
// handles operational buildings and construction sites alike.
|
|
const std::optional<BuildingConfig> config = readBuildingConfig(sim, e.id);
|
|
if (config.has_value())
|
|
{
|
|
building.recipeId = config->recipeId.value_or(std::string());
|
|
building.shipLayout = config->shipLayout;
|
|
building.splitterFilterA = config->splitterFilterA;
|
|
building.splitterFilterB = config->splitterFilterB;
|
|
}
|
|
blueprint.buildings.push_back(building);
|
|
}
|
|
return blueprint;
|
|
}
|
|
|
|
bool selectionHasPlaceableBuilding(const Simulation& sim,
|
|
const std::vector<BuildingId>& selectedIds)
|
|
{
|
|
for (const BuildingId id : selectedIds)
|
|
{
|
|
if (resolvePlaceable(sim, id).has_value())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|