#include "BuildingConfig.h" #include #include #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* 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 resolvePlaceable(const Simulation& sim, BuildingId id) { const Building* building = sim.buildings().findBuilding(id); const ConstructionSite* site = building ? nullptr : sim.buildings().findSite(id); if (!building && !site) { return std::nullopt; } const BuildingType type = building ? building->type : site->type; const BuildingDef* def = sim.config().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 readBuildingConfig(const Simulation& sim, BuildingId id) { const Building* building = sim.buildings().findBuilding(id); const ConstructionSite* site = building ? nullptr : sim.buildings().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 info = sim.belts().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& selectedIds) { std::vector entries; entries.reserve(selectedIds.size()); for (const BuildingId id : selectedIds) { const std::optional 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 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& selectedIds) { for (const BuildingId id : selectedIds) { if (resolvePlaceable(sim, id).has_value()) { return true; } } return false; }