168 lines
5.3 KiB
C++
168 lines
5.3 KiB
C++
#include "BuildingConfig.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 = sim.getBuildings().findBuilding(id);
|
|
const ConstructionSite* site = building ? nullptr : sim.getBuildings().findSite(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 = sim.getBuildings().findBuilding(id);
|
|
const ConstructionSite* site = building ? nullptr : sim.getBuildings().findSite(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;
|
|
}
|