Allow creating blueprints from construction sites

This commit is contained in:
2026-07-13 20:50:13 +02:00
parent 698dd4d13d
commit 8c4fb78fc9
5 changed files with 225 additions and 69 deletions

View File

@@ -8,6 +8,7 @@
#include "Blueprint.h"
#include "Building.h"
#include "BuildingConfig.h"
#include "BuildingsConfig.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
@@ -696,6 +697,92 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction
REQUIRE(b->recipeId == "mine_copper_ore");
}
// ---------------------------------------------------------------------------
// Blueprint capture from construction sites (REQ-UI-BLUEPRINT-CREATE,
// REQ-UI-BLUEPRINT-STORAGE): a still-under-construction building is a valid
// blueprint source, captured identically to an operational building.
// ---------------------------------------------------------------------------
TEST_CASE("Blueprint creation: a construction site is captured", "[blueprint]")
{
Simulation sim(loadConfig());
// Freshly placed → a ConstructionSite (not ticked to completion). A 1x1 belt keeps
// the body-cell bounding-box centered on the anchor, so a single site → zero offset.
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Belt, QPoint(-2, 0), Rotation::East);
REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.buildings().findSite(id) != nullptr);
REQUIRE(sim.buildings().findBuilding(id) == nullptr);
const Blueprint bp = captureBlueprintFromSelection(sim, { id });
REQUIRE(bp.buildings.size() == 1);
REQUIRE(bp.buildings[0].type == BuildingType::Belt);
REQUIRE(bp.buildings[0].offset == QPoint(0, 0)); // single 1x1 building → zero offset
}
TEST_CASE("Blueprint creation: a construction site's recipe is captured", "[blueprint]")
{
Simulation sim(loadConfig());
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
const Blueprint bp = captureBlueprintFromSelection(sim, { id });
REQUIRE(bp.buildings.size() == 1);
REQUIRE(bp.buildings[0].recipeId == "mine_iron_ore");
}
TEST_CASE("Blueprint creation: mixed operational building and construction site are both captured",
"[blueprint]")
{
Simulation sim(loadConfig());
// Building A: place, configure, and tick to completion so it is operational.
const BuildingId idA =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
REQUIRE(idA != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idA, "mine_iron_ore");
for (int i = 0; i <= static_cast<int>(secondsToTicks(10.0)); ++i) { sim.tick(); }
REQUIRE(sim.buildings().findBuilding(idA) != nullptr);
// Building B: place and configure, but leave as a construction site.
const BuildingId idB =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East);
REQUIRE(idB != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idB, "mine_copper_ore");
REQUIRE(sim.buildings().findSite(idB) != nullptr);
const Blueprint bp = captureBlueprintFromSelection(sim, { idA, idB });
REQUIRE(bp.buildings.size() == 2);
REQUIRE(bp.buildings[0].type == BuildingType::Miner);
REQUIRE(bp.buildings[1].type == BuildingType::Miner);
// Both the operational building's and the site's configs come through.
std::vector<std::string> recipes = { bp.buildings[0].recipeId, bp.buildings[1].recipeId };
std::sort(recipes.begin(), recipes.end());
REQUIRE(recipes == std::vector<std::string>{ "mine_copper_ore", "mine_iron_ore" });
// Distinct anchors → distinct offsets.
REQUIRE(bp.buildings[0].offset != bp.buildings[1].offset);
}
TEST_CASE("Blueprint creation: selectionHasPlaceableBuilding sees a construction site", "[blueprint]")
{
Simulation sim(loadConfig());
REQUIRE_FALSE(selectionHasPlaceableBuilding(sim, {}));
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
REQUIRE(id != kInvalidBuildingId);
REQUIRE(sim.buildings().findSite(id) != nullptr);
REQUIRE(selectionHasPlaceableBuilding(sim, { id }));
}
TEST_CASE("Blueprint placement: interceptor schematic is unlocked at game start", "[blueprint]")
{
// "interceptor" has unlock_at_station_level = -1 in the test config.