re-cover copy-settings through single-building blueprints

This commit is contained in:
2026-08-06 19:44:55 +02:00
parent 9a3b6c10d6
commit 08d8b0dd90
13 changed files with 542 additions and 65 deletions

View File

@@ -69,16 +69,9 @@ bool isPlacementValid(const FactoryState& state, const GameConfig& config,Buildi
}
std::optional<BuildingId> findRotateInPlaceTarget(const FactoryState& state, const GameConfig& config,
BuildingType type, QPoint anchor, Rotation rot)
std::optional<CoincidingBuilding> findCoincidingSameTypeBuilding(const FactoryState& state,
const GameConfig& config, BuildingType type, QPoint anchor, Rotation rot)
{
// Tunnel Entries and Tunnel Exits cannot be rotated in place; re-orienting a
// tunnel requires deconstructing and re-placing it (REQ-BLD-ROTATE-IN-PLACE).
if (type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit)
{
return std::nullopt;
}
const BuildingDef* def = config.buildings.findBuildingDef(type);
if (!def) { return std::nullopt; }
@@ -106,20 +99,88 @@ std::optional<BuildingId> findRotateInPlaceTarget(const FactoryState& state, con
if (site.id != candidateId) { continue; }
if (site.type != type) { return std::nullopt; }
if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
return candidateId;
return CoincidingBuilding{candidateId, site.rotation};
}
for (const Building& b : state.buildings)
{
if (b.id != candidateId) { continue; }
if (b.type != type) { return std::nullopt; }
if (b.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
return candidateId;
return CoincidingBuilding{candidateId, b.rotation};
}
return std::nullopt;
}
std::optional<BuildingId> findRotateInPlaceTarget(const FactoryState& state, const GameConfig& config,
BuildingType type, QPoint anchor, Rotation rot)
{
// Tunnel Entries and Tunnel Exits cannot be rotated in place; re-orienting a
// tunnel requires deconstructing and re-placing it (REQ-BLD-ROTATE-IN-PLACE).
if (type == BuildingType::TunnelEntry || type == BuildingType::TunnelExit)
{
return std::nullopt;
}
const std::optional<CoincidingBuilding> target =
findCoincidingSameTypeBuilding(state, config, type, anchor, rot);
if (!target.has_value()) { return std::nullopt; }
return target->id;
}
BlueprintGhostResolved resolveBlueprintGhost(const FactoryState& state, const GameConfig& config,
BuildingType type, QPoint anchor, Rotation rotation, bool blueprintHoldsOneBuilding)
{
// Terrain and world bounds first: nothing rescues a ghost hanging off the asteroid
// (REQ-BLD-PLACE-VALID condition (a)).
if (!isPlacementValid(state, config, type, anchor, rotation))
{
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt};
}
const std::optional<CoincidingBuilding> coinciding =
findCoincidingSameTypeBuilding(state, config, type, anchor, rotation);
if (coinciding.has_value())
{
// A single configurable building hands its settings over, whatever way the target
// faces: a transfer never rotates anything, so the target's facing does not matter
// (REQ-UI-BLUEPRINT-TRANSFER). Tested before the overlap rule, which then governs
// only what this does not claim.
if (blueprintHoldsOneBuilding && isConfigurableBuildingType(type))
{
return BlueprintGhostResolved{BlueprintGhostAction::Transfer, coinciding->id};
}
// Otherwise the building the blueprint wants must already be there in full,
// facing the same way, because nothing here may re-orient it
// (REQ-UI-BLUEPRINT-OVERLAP). Tunnels are not excluded: nothing is rotated, so
// the reason for their REQ-BLD-ROTATE-IN-PLACE exception does not arise.
if (coinciding->rotation == rotation)
{
return BlueprintGhostResolved{BlueprintGhostAction::CompatibleOverlap,
coinciding->id};
}
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt};
}
// No coincidence: any occupancy at all is an ordinary overlap
// (REQ-BLD-PLACE-VALID condition (b)).
const BuildingDef* def = config.buildings.findBuildingDef(type);
if (!def) { return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt}; }
const ParsedSurfaceMask parsed = parseSurfaceMask(def->surfaceMask, rotation);
for (const QPoint& relativeCell : parsed.bodyCells)
{
if (isTileOccupied(state, anchor + relativeCell))
{
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt};
}
}
return BlueprintGhostResolved{BlueprintGhostAction::PlaceNew, std::nullopt};
}
bool canPlaceBuilding(const FactoryState& state, const GameConfig& config,
BuildingType type, QPoint anchor, Rotation rotation)
{