#include "catch.hpp" #include "FactoryQueries.h" #include #include "Building.h" #include "BuildingConfig.h" #include "BuildingSystem.h" #include "BuildingType.h" #include "ConfigLoader.h" #include "GameConfig.h" #include "Rotation.h" #include "ShipLayout.h" #include "ShipsConfig.h" #include "Simulation.h" #include "SimulationTestAccess.h" #include "TestConfig.h" // readBuildingConfig underpins the copy-settings gesture (REQ-BLD-COPY-CONFIG): // it extracts a building's recipe / schematic / layout / splitter filters so they // can be stamped onto a same-type building. It reads operational buildings and // construction sites alike. namespace { const BuildingDef* findDef(const GameConfig& cfg, BuildingType type) { for (const BuildingDef& def : cfg.buildings.buildings) { if (def.type == type) { return &def; } } return nullptr; } BuildingId placeOperational(Simulation& sim, const GameConfig& cfg, BuildingType type, QPoint anchor) { const BuildingDef* def = findDef(cfg, type); REQUIRE(def != nullptr); return SimulationTestAccess::buildings(sim).placeImmediate(SimulationTestAccess::state(sim), type, def->surfaceMask, anchor, Rotation::East); } const ShipDef* findAvailableSchematic(const GameConfig& cfg) { for (const ShipDef& def : cfg.ships.ships) { // A ship starts unlocked iff no unlock group grants it (REQ-LOCK-EXPLICIT). bool granted = false; for (const UnlockGroupDef& group : cfg.unlocks.groups) { if (std::find(group.ships.begin(), group.ships.end(), def.id) != group.ships.end()) { granted = true; break; } } if (!granted) { return &def; } } return nullptr; } } // namespace TEST_CASE("readBuildingConfig returns a miner's selected recipe", "[copyconfig]") { const GameConfig cfg = loadTestConfig(); Simulation sim(loadTestConfig(), 7); const BuildingId id = placeOperational(sim, cfg, BuildingType::Miner, QPoint(0, 0)); SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore"); const std::optional config = readBuildingConfig(sim, id); REQUIRE(config.has_value()); CHECK(config->type == BuildingType::Miner); REQUIRE(config->recipeId.has_value()); CHECK(*config->recipeId == "mine_iron_ore"); CHECK_FALSE(config->isSplitter); CHECK_FALSE(config->shipLayout.has_value()); } TEST_CASE("readBuildingConfig leaves recipe unset when nothing is selected", "[copyconfig]") { const GameConfig cfg = loadTestConfig(); Simulation sim(loadTestConfig(), 7); const BuildingId id = placeOperational(sim, cfg, BuildingType::Assembler, QPoint(0, 0)); const std::optional config = readBuildingConfig(sim, id); REQUIRE(config.has_value()); CHECK(config->type == BuildingType::Assembler); CHECK_FALSE(config->recipeId.has_value()); // nothing to copy CHECK_FALSE(config->isSplitter); } TEST_CASE("readBuildingConfig returns a shipyard's schematic and layout", "[copyconfig]") { const GameConfig cfg = loadTestConfig(); Simulation sim(loadTestConfig(), 7); const ShipDef* schematic = findAvailableSchematic(cfg); REQUIRE(schematic != nullptr); const BuildingId id = placeOperational(sim, cfg, BuildingType::Shipyard, QPoint(0, 0)); SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, schematic->id); SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), id, ShipLayoutConfig{}); const std::optional config = readBuildingConfig(sim, id); REQUIRE(config.has_value()); CHECK(config->type == BuildingType::Shipyard); REQUIRE(config->recipeId.has_value()); CHECK(*config->recipeId == schematic->id); CHECK(config->shipLayout.has_value()); } TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]") { const GameConfig cfg = loadTestConfig(); Simulation sim(loadTestConfig(), 7); // A placed miner enters the construction queue as a site (not yet operational). const BuildingId id = SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value(); REQUIRE(id != kInvalidBuildingId); REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr); REQUIRE(findSite(sim.getFactoryState(), id) != nullptr); SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore"); const std::optional config = readBuildingConfig(sim, id); REQUIRE(config.has_value()); CHECK(config->type == BuildingType::Miner); REQUIRE(config->recipeId.has_value()); CHECK(*config->recipeId == "mine_iron_ore"); } TEST_CASE("readBuildingConfig returns nullopt for an unknown id", "[copyconfig]") { Simulation sim(loadTestConfig(), 7); CHECK_FALSE(readBuildingConfig(sim, kInvalidBuildingId).has_value()); }