implement storing recipes in blueprint

This commit is contained in:
2026-04-27 12:58:44 +02:00
parent 3da3ef5c5b
commit 541b8fdaee
4 changed files with 102 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <vector>
#include <QPoint>
@@ -12,7 +13,8 @@ struct BlueprintBuilding
{
BuildingType type;
Rotation rotation;
QPoint offset; // tile offset from bounding-box center (floor for even sizes)
QPoint offset; // tile offset from bounding-box center (floor for even sizes)
std::string recipeId; // empty = none selected
};
struct Blueprint

View File

@@ -7,6 +7,7 @@
#include <QPoint>
#include "Blueprint.h"
#include "Building.h"
#include "BuildingsConfig.h"
#include "BuildingSystem.h"
#include "BuildingType.h"
@@ -15,6 +16,7 @@
#include "Rotation.h"
#include "Simulation.h"
#include "SurfaceMask.h"
#include "Tick.h"
// ---------------------------------------------------------------------------
// Helpers that mirror the production implementations under test.
@@ -58,6 +60,7 @@ struct BuildingSpec
std::vector<QPoint> bodyCells;
BuildingType type;
Rotation rotation;
std::string recipeId; // empty = none
};
static Blueprint buildBlueprint(const std::vector<BuildingSpec>& specs)
@@ -83,6 +86,7 @@ static Blueprint buildBlueprint(const std::vector<BuildingSpec>& specs)
bb.type = s.type;
bb.rotation = s.rotation;
bb.offset = s.anchor - center;
bb.recipeId = s.recipeId;
bp.buildings.push_back(bb);
}
return bp;
@@ -581,3 +585,83 @@ TEST_CASE("Blueprint placement: insufficient blocks returns kInvalidEntityId and
REQUIRE(id == kInvalidEntityId);
REQUIRE(sim.buildingBlocksStock() == blocksBeforeAttempt);
}
// ---------------------------------------------------------------------------
// Recipe / schematic capture and re-application
// ---------------------------------------------------------------------------
TEST_CASE("Blueprint: recipeId is stored in BlueprintBuilding", "[blueprint]")
{
const BuildingSpec spec{
QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Miner, Rotation::East,
"mine_iron_ore"
};
const Blueprint bp = buildBlueprint({ spec });
REQUIRE(bp.buildings.size() == 1);
REQUIRE(bp.buildings[0].recipeId == "mine_iron_ore");
}
TEST_CASE("Blueprint: building with no recipe has empty recipeId", "[blueprint]")
{
const BuildingSpec spec{
QPoint(-5, 0), {QPoint(-5, 0)}, BuildingType::Belt, Rotation::East
// recipeId defaults to ""
};
const Blueprint bp = buildBlueprint({ spec });
REQUIRE(bp.buildings[0].recipeId.empty());
}
TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", "[blueprint]")
{
Simulation sim(loadConfig());
// Miner body cells: (0,0),(1,0),(0,1) — all at x < 0, valid for asteroid.
const EntityId id = sim.tryPlaceBuilding(BuildingType::Miner, QPoint(-2, 0), Rotation::East);
REQUIRE(id != kInvalidEntityId);
sim.buildings().setRecipe(id, "mine_iron_ore");
const ConstructionSite* site = sim.buildings().findSite(id);
REQUIRE(site != nullptr);
REQUIRE(site->recipeId == "mine_iron_ore");
}
TEST_CASE("Blueprint placement: recipe transfers to building after construction completes",
"[blueprint]")
{
Simulation sim(loadConfig());
const EntityId id = sim.tryPlaceBuilding(BuildingType::Miner, QPoint(-2, 0), Rotation::East);
REQUIRE(id != kInvalidEntityId);
sim.buildings().setRecipe(id, "mine_copper_ore");
// Miner construction_time_seconds = 10 → completesAt = secondsToTicks(10) = 300.
// Run 301 ticks (0..300) to process the completion tick.
for (int i = 0; i <= static_cast<int>(secondsToTicks(10.0)); ++i)
{
sim.tick();
}
const Building* b = sim.buildings().findBuilding(id);
REQUIRE(b != nullptr);
REQUIRE(b->recipeId == "mine_copper_ore");
}
TEST_CASE("Blueprint placement: interceptor schematic is unlocked at game start", "[blueprint]")
{
// "interceptor" has available_from_start = true in the test config.
// This confirms the guard in placeBlueprintAtTile passes for start-unlocked schematics.
Simulation sim(loadConfig());
REQUIRE(sim.isSchematicUnlocked("interceptor"));
}
TEST_CASE("Blueprint placement: repair_ship schematic is locked at game start", "[blueprint]")
{
// "repair_ship" has available_from_start = false in the test config.
// This confirms the guard in placeBlueprintAtTile blocks locked schematics,
// leaving the shipyard's schematic unset.
Simulation sim(loadConfig());
REQUIRE_FALSE(sim.isSchematicUnlocked("repair_ship"));
}

View File

@@ -188,6 +188,7 @@ Blueprint BlueprintPanel::createBlueprintFromSelection() const
bb.type = e.building->type;
bb.rotation = e.building->rotation;
bb.offset = e.building->anchor - center;
bb.recipeId = e.building->recipeId;
bp.buildings.push_back(bb);
}
return bp;

View File

@@ -472,7 +472,20 @@ void GameWorldView::placeBlueprintAtTile(QPoint center)
for (const BlueprintBuilding& bb : bp.buildings)
{
m_sim->tryPlaceBuilding(bb.type, center + bb.offset, bb.rotation);
const EntityId id = m_sim->tryPlaceBuilding(bb.type, center + bb.offset, bb.rotation);
if (id == kInvalidEntityId || bb.recipeId.empty()) { continue; }
if (bb.type == BuildingType::Shipyard)
{
if (m_sim->isSchematicUnlocked(bb.recipeId))
{
m_sim->buildings().setRecipe(id, bb.recipeId);
}
}
else
{
m_sim->buildings().setRecipe(id, bb.recipeId);
}
}
}