Files
dota_factory/src/lib/sim/BuildingConfig.cpp
Malte Langkabel df7f60c898 migrate every factory query off BuildingSystem onto the free functions
The eleven forwarding members added last commit are gone; callers now read the
data directly through FactoryQueries.h. Simulation and ArenaSimulation expose
getFactoryState() so the UI, the balancing view and the tests can reach it.

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

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

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

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

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
2026-08-04 21:52:49 +02:00

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;
}