re-cover copy-settings through single-building blueprints
This commit is contained in:
@@ -1039,6 +1039,206 @@ TEST_CASE("BuildingSystem: findRotateInPlaceTarget works for a symmetric multi-t
|
||||
REQUIRE(*result == id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// resolveBlueprintGhost
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// What a blueprint ghost does where it meets an existing building
|
||||
// (REQ-UI-BLUEPRINT-OVERLAP, REQ-UI-BLUEPRINT-TRANSFER). Blueprint placement never
|
||||
// rotates anything, so a coinciding building must either take the blueprint's settings
|
||||
// or already match it exactly.
|
||||
|
||||
namespace
|
||||
{
|
||||
BlueprintGhostResolved resolveOne(const PlacementFixture& f, BuildingType type,
|
||||
QPoint anchor, Rotation rotation)
|
||||
{
|
||||
return resolveBlueprintGhost(f.state, f.cfg, type, anchor, rotation,
|
||||
/*blueprintHoldsOneBuilding*/ true);
|
||||
}
|
||||
|
||||
BlueprintGhostResolved resolveInConstellation(const PlacementFixture& f, BuildingType type,
|
||||
QPoint anchor, Rotation rotation)
|
||||
{
|
||||
return resolveBlueprintGhost(f.state, f.cfg, type, anchor, rotation,
|
||||
/*blueprintHoldsOneBuilding*/ false);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("isConfigurableBuildingType: only types with player-facing settings",
|
||||
"[blueprint]")
|
||||
{
|
||||
// The gate on whether a single-building blueprint transfers anything at all.
|
||||
CHECK(isConfigurableBuildingType(BuildingType::Miner));
|
||||
CHECK(isConfigurableBuildingType(BuildingType::Assembler));
|
||||
CHECK(isConfigurableBuildingType(BuildingType::Shipyard));
|
||||
CHECK(isConfigurableBuildingType(BuildingType::Splitter));
|
||||
|
||||
// Smelter and Reprocessing Plant run implicit recipes (REQ-BLD-SMELTER,
|
||||
// REQ-BLD-REPROCESSING) and the rest have no settings whatsoever.
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::Smelter));
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::ReprocessingPlant));
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::SalvageBay));
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::Belt));
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::TunnelEntry));
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::TunnelExit));
|
||||
CHECK_FALSE(isConfigurableBuildingType(BuildingType::Hq));
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: free valid cells place a new building", "[blueprint]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
// Anchored on the asteroid (x < 0): a miner is all-asteroid cells. BuildingSystem's
|
||||
// place() skips the terrain rules, but resolveBlueprintGhost applies them.
|
||||
const BlueprintGhostResolved resolved =
|
||||
resolveOne(f, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
|
||||
CHECK(resolved.action == BlueprintGhostAction::PlaceNew);
|
||||
CHECK_FALSE(resolved.targetId.has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: terrain-invalid positions are invalid", "[blueprint]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
// A miner is all-asteroid (A) cells, so it cannot sit out in space (x >= 0).
|
||||
CHECK(resolveOne(f, BuildingType::Miner, QPoint(5, 0), Rotation::East).action
|
||||
== BlueprintGhostAction::Invalid);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: overlapping a different building type is invalid",
|
||||
"[blueprint]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
f.bs.place(f.state, BuildingType::Belt, QPoint(-1, 0), Rotation::East, 0);
|
||||
|
||||
CHECK(resolveOne(f, BuildingType::Splitter, QPoint(-1, 0), Rotation::East).action
|
||||
== BlueprintGhostAction::Invalid);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: a partial overlap of the same type is invalid",
|
||||
"[blueprint]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
// Smelter at (-3,0) covers (-3,0),(-2,0),(-3,1),(-2,1); a ghost at (-2,0) covers only
|
||||
// two of those, so it coincides with nothing and is an ordinary occupied overlap.
|
||||
// Both footprints stay on the asteroid, so terrain is not what fails here.
|
||||
f.bs.place(f.state, BuildingType::Smelter, QPoint(-3, 0), Rotation::East, 0);
|
||||
|
||||
CHECK(resolveOne(f, BuildingType::Smelter, QPoint(-2, 0), Rotation::East).action
|
||||
== BlueprintGhostAction::Invalid);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: a single configurable building transfers its settings",
|
||||
"[blueprint]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
const BuildingId id =
|
||||
f.bs.place(f.state, BuildingType::Miner, QPoint(-2, 0), Rotation::East, 0).value();
|
||||
|
||||
const BlueprintGhostResolved resolved =
|
||||
resolveOne(f, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
|
||||
REQUIRE(resolved.action == BlueprintGhostAction::Transfer);
|
||||
REQUIRE(resolved.targetId.has_value());
|
||||
CHECK(*resolved.targetId == id);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: a transfer ignores the target's rotation", "[blueprint]")
|
||||
{
|
||||
// The rule transfer does not share with compatible overlap: a transfer never rotates
|
||||
// anything, so which way the target faces cannot matter (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
PlacementFixture f;
|
||||
|
||||
const BuildingId id =
|
||||
f.bs.place(f.state, BuildingType::Splitter, QPoint(-1, 0), Rotation::East, 0).value();
|
||||
|
||||
const BlueprintGhostResolved resolved =
|
||||
resolveOne(f, BuildingType::Splitter, QPoint(-1, 0), Rotation::North);
|
||||
REQUIRE(resolved.action == BlueprintGhostAction::Transfer);
|
||||
REQUIRE(resolved.targetId.has_value());
|
||||
CHECK(*resolved.targetId == id);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: a construction site is a transfer target too",
|
||||
"[blueprint]")
|
||||
{
|
||||
PlacementFixture f;
|
||||
|
||||
// Not ticked to completion, so it is still queued (REQ-BLD-SITE-CONFIG).
|
||||
const BuildingId id =
|
||||
f.bs.place(f.state, BuildingType::Assembler, QPoint(-3, 0), Rotation::East, 0).value();
|
||||
REQUIRE_FALSE(getAllSites(f.state).empty());
|
||||
|
||||
const BlueprintGhostResolved resolved =
|
||||
resolveOne(f, BuildingType::Assembler, QPoint(-3, 0), Rotation::East);
|
||||
REQUIRE(resolved.action == BlueprintGhostAction::Transfer);
|
||||
CHECK(*resolved.targetId == id);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: a single building with no settings overlaps instead",
|
||||
"[blueprint]")
|
||||
{
|
||||
// A belt carries nothing to transfer, so the same footprint is a compatible overlap
|
||||
// when the facings match -- and invalid when they do not, since nothing here may
|
||||
// re-orient it (REQ-UI-BLUEPRINT-OVERLAP).
|
||||
PlacementFixture f;
|
||||
|
||||
const BuildingId id =
|
||||
f.bs.place(f.state, BuildingType::Belt, QPoint(-1, 0), Rotation::East, 0).value();
|
||||
|
||||
const BlueprintGhostResolved matching =
|
||||
resolveOne(f, BuildingType::Belt, QPoint(-1, 0), Rotation::East);
|
||||
REQUIRE(matching.action == BlueprintGhostAction::CompatibleOverlap);
|
||||
CHECK(*matching.targetId == id);
|
||||
|
||||
CHECK(resolveOne(f, BuildingType::Belt, QPoint(-1, 0), Rotation::North).action
|
||||
== BlueprintGhostAction::Invalid);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: nothing in a multi-building blueprint transfers",
|
||||
"[blueprint]")
|
||||
{
|
||||
// Even a configurable building coinciding with its twin only overlaps once the
|
||||
// blueprint holds more than one building (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
PlacementFixture f;
|
||||
|
||||
const BuildingId id =
|
||||
f.bs.place(f.state, BuildingType::Miner, QPoint(-2, 0), Rotation::East, 0).value();
|
||||
|
||||
const BlueprintGhostResolved matching =
|
||||
resolveInConstellation(f, BuildingType::Miner, QPoint(-2, 0), Rotation::East);
|
||||
REQUIRE(matching.action == BlueprintGhostAction::CompatibleOverlap);
|
||||
CHECK(*matching.targetId == id);
|
||||
|
||||
// ... and a differently-facing twin blocks the whole constellation.
|
||||
CHECK(resolveInConstellation(f, BuildingType::Miner, QPoint(-2, 0), Rotation::North).action
|
||||
== BlueprintGhostAction::Invalid);
|
||||
}
|
||||
|
||||
TEST_CASE("resolveBlueprintGhost: an identical tunnel is a compatible overlap", "[blueprint]")
|
||||
{
|
||||
// findRotateInPlaceTarget refuses tunnels because re-orienting one is unsupported.
|
||||
// Nothing is re-oriented here, so that reason does not apply and the tunnel the
|
||||
// blueprint wants -- already there, same facing -- is simply left alone.
|
||||
PlacementFixture f;
|
||||
|
||||
const BuildingId id =
|
||||
f.bs.place(f.state, BuildingType::TunnelEntry, QPoint(-1, 0), Rotation::East, 0).value();
|
||||
f.bs.place(f.state, BuildingType::TunnelExit, QPoint(-2, 0), Rotation::East, 0);
|
||||
|
||||
REQUIRE_FALSE(
|
||||
findRotateInPlaceTarget(f.state, f.cfg, BuildingType::TunnelEntry, QPoint(-1, 0), Rotation::East)
|
||||
.has_value());
|
||||
|
||||
const BlueprintGhostResolved resolved =
|
||||
resolveInConstellation(f, BuildingType::TunnelEntry, QPoint(-1, 0), Rotation::East);
|
||||
REQUIRE(resolved.action == BlueprintGhostAction::CompatibleOverlap);
|
||||
CHECK(*resolved.targetId == id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// rotateInPlace
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user