store ship module layout in shipyard blueprint

This commit is contained in:
2026-06-04 21:38:46 +02:00
parent 42b51cc6f4
commit 3e19e44f24
6 changed files with 252 additions and 18 deletions

View File

@@ -14,6 +14,7 @@
#include "ConfigLoader.h"
#include "BuildingId.h"
#include "Rotation.h"
#include "ShipLayout.h"
#include "Simulation.h"
#include "SurfaceMask.h"
#include "Tick.h"
@@ -61,6 +62,7 @@ struct BuildingSpec
BuildingType type;
Rotation rotation;
std::string recipeId; // empty = none
std::optional<ShipLayoutConfig> shipLayout;
};
static Blueprint buildBlueprint(const std::vector<BuildingSpec>& specs)
@@ -83,10 +85,11 @@ static Blueprint buildBlueprint(const std::vector<BuildingSpec>& specs)
for (const BuildingSpec& s : specs)
{
BlueprintBuilding bb;
bb.type = s.type;
bb.rotation = s.rotation;
bb.offset = s.anchor - center;
bb.recipeId = s.recipeId;
bb.type = s.type;
bb.rotation = s.rotation;
bb.offset = s.anchor - center;
bb.recipeId = s.recipeId;
bb.shipLayout = s.shipLayout;
bp.buildings.push_back(bb);
}
return bp;
@@ -665,3 +668,108 @@ TEST_CASE("Blueprint placement: repair_ship schematic is locked at game start",
Simulation sim(loadConfig());
REQUIRE_FALSE(sim.isSchematicUnlocked("repair_ship"));
}
// ---------------------------------------------------------------------------
// Ship layout capture and re-application via blueprints
// ---------------------------------------------------------------------------
TEST_CASE("Blueprint: shipLayout is captured in BlueprintBuilding", "[blueprint]")
{
// sensor_booster has surface_mask ["O"] — fits a single buildable cell.
// The interceptor layout ["XOX","OOO","XOX"] has a buildable cell at (1, 0).
ShipLayoutConfig layout;
PlacedModule pm;
pm.moduleId = "sensor_booster";
pm.position = QPoint(1, 0);
pm.rotation = Rotation::North;
layout.placedModules.push_back(pm);
const BuildingSpec spec{
QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Shipyard, Rotation::East,
"interceptor", layout
};
const Blueprint bp = buildBlueprint({ spec });
REQUIRE(bp.buildings.size() == 1);
REQUIRE(bp.buildings[0].shipLayout.has_value());
REQUIRE(bp.buildings[0].shipLayout->placedModules.size() == 1);
REQUIRE(bp.buildings[0].shipLayout->placedModules[0].moduleId == "sensor_booster");
REQUIRE(bp.buildings[0].shipLayout->placedModules[0].position == QPoint(1, 0));
REQUIRE(bp.buildings[0].shipLayout->placedModules[0].rotation == Rotation::North);
}
TEST_CASE("Blueprint: building without layout has nullopt shipLayout", "[blueprint]")
{
const BuildingSpec spec{
QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Shipyard, Rotation::East, "interceptor"
};
const Blueprint bp = buildBlueprint({ spec });
REQUIRE(bp.buildings.size() == 1);
REQUIRE_FALSE(bp.buildings[0].shipLayout.has_value());
}
TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout", "[blueprint]")
{
Simulation sim(loadConfig());
// Shipyard surface_mask ["AAAS>","AAAS "] with Rotation::East:
// A-tiles at (-3,0),(-2,0),(-1,0),(-3,1),(-2,1),(-1,1) — all x < 0, valid asteroid tiles.
// S-tile at (0,0) and (0,1) — x >= 0, valid space tiles.
const BuildingId id = sim.tryPlaceBuilding(BuildingType::Shipyard, QPoint(-3, 0), Rotation::East);
REQUIRE(id != kInvalidBuildingId);
ShipLayoutConfig layout;
PlacedModule pm;
pm.moduleId = "laser_cannon";
pm.position = QPoint(1, 1);
pm.rotation = Rotation::East;
layout.placedModules.push_back(pm);
sim.buildings().setShipLayout(id, layout);
const ConstructionSite* site = sim.buildings().findSite(id);
REQUIRE(site != nullptr);
REQUIRE(site->shipLayout.has_value());
REQUIRE(site->shipLayout->placedModules.size() == 1);
REQUIRE(site->shipLayout->placedModules[0].moduleId == "laser_cannon");
}
TEST_CASE("Blueprint placement: ship layout transfers to building after construction completes",
"[blueprint]")
{
Simulation sim(loadConfig());
const BuildingId id = sim.tryPlaceBuilding(BuildingType::Shipyard, QPoint(-3, 0), Rotation::East);
REQUIRE(id != kInvalidBuildingId);
ShipLayoutConfig layout;
PlacedModule pm;
pm.moduleId = "sensor_booster";
pm.position = QPoint(1, 0);
pm.rotation = Rotation::North;
layout.placedModules.push_back(pm);
sim.buildings().setShipLayout(id, layout);
// Shipyard construction_time_seconds = 30 in the test config.
double constructionTime = 0.0;
for (const BuildingDef& def : sim.config().buildings.buildings)
{
if (def.type == BuildingType::Shipyard) { constructionTime = def.constructionTimeSeconds; break; }
}
REQUIRE(constructionTime > 0.0);
for (int i = 0; i <= static_cast<int>(secondsToTicks(constructionTime)); ++i)
{
sim.tick();
}
const Building* b = sim.buildings().findBuilding(id);
REQUIRE(b != nullptr);
REQUIRE(b->shipLayout.has_value());
REQUIRE(b->shipLayout->placedModules.size() == 1);
REQUIRE(b->shipLayout->placedModules[0].moduleId == "sensor_booster");
REQUIRE(b->shipLayout->placedModules[0].position == QPoint(1, 0));
REQUIRE(b->shipLayout->placedModules[0].rotation == Rotation::North);
}