target single-building transfers by hovering, not by footprint coincidence
This commit is contained in:
@@ -130,22 +130,67 @@ std::optional<BuildingId> findRotateInPlaceTarget(const FactoryState& state, con
|
||||
}
|
||||
|
||||
|
||||
BlueprintGhostResolved resolveBlueprintGhost(const FactoryState& state, const GameConfig& config,
|
||||
BuildingType type, QPoint anchor, Rotation rotation, bool blueprintHoldsOneBuilding)
|
||||
namespace
|
||||
{
|
||||
// Terrain and world bounds first: nothing rescues a ghost hanging off the asteroid
|
||||
|
||||
// The building or construction site occupying `tile`, if it is of `type`, together with
|
||||
// where it stands. The single-building transfer gesture hit-tests the cursor with this
|
||||
// instead of comparing footprints (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
std::optional<CoincidingBuilding> findSameTypeBuildingAt(const FactoryState& state,
|
||||
BuildingType type, QPoint tile,
|
||||
QPoint& anchorOut)
|
||||
{
|
||||
const std::optional<BuildingId> owner = state.grid.findOwner(tile);
|
||||
if (!owner.has_value()) { return std::nullopt; }
|
||||
|
||||
if (const ConstructionSite* site = findSite(state, *owner))
|
||||
{
|
||||
if (site->type != type) { return std::nullopt; }
|
||||
anchorOut = site->anchor;
|
||||
return CoincidingBuilding{site->id, site->rotation};
|
||||
}
|
||||
if (const Building* building = findBuilding(state, *owner))
|
||||
{
|
||||
if (building->type != type) { return std::nullopt; }
|
||||
anchorOut = building->anchor;
|
||||
return CoincidingBuilding{building->id, building->rotation};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
BlueprintGhostResolved resolveBlueprintGhost(const FactoryState& state, const GameConfig& config,
|
||||
BuildingType type, QPoint anchor, Rotation rotation, std::optional<QPoint> hoverTile)
|
||||
{
|
||||
// The single-building gesture first, because it answers without looking at the
|
||||
// ghost's own position at all: whatever same-type building the cursor is on takes the
|
||||
// settings, at any facing, and the ghost snaps onto it (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
if (hoverTile.has_value() && isConfigurableBuildingType(type))
|
||||
{
|
||||
QPoint hoveredAnchor;
|
||||
const std::optional<CoincidingBuilding> hovered =
|
||||
findSameTypeBuildingAt(state, type, *hoverTile, hoveredAnchor);
|
||||
if (hovered.has_value())
|
||||
{
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::Transfer, hovered->id,
|
||||
hoveredAnchor, hovered->rotation};
|
||||
}
|
||||
}
|
||||
|
||||
// Terrain and world bounds next: 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};
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
|
||||
anchor, rotation};
|
||||
}
|
||||
|
||||
const std::optional<CoincidingBuilding> coinciding =
|
||||
findCoincidingSameTypeBuilding(state, config, type, anchor, rotation);
|
||||
if (coinciding.has_value())
|
||||
{
|
||||
const bool configurable = isConfigurableBuildingType(type);
|
||||
|
||||
// The building the blueprint wants is already there, facing the same way. It
|
||||
// takes the blueprint's settings if it has any to take (REQ-UI-BLUEPRINT-TRANSFER)
|
||||
// and is otherwise left exactly as it is (REQ-UI-BLUEPRINT-OVERLAP). Tunnels are
|
||||
@@ -153,36 +198,36 @@ BlueprintGhostResolved resolveBlueprintGhost(const FactoryState& state, const Ga
|
||||
// REQ-BLD-ROTATE-IN-PLACE exception does not arise.
|
||||
if (coinciding->rotation == rotation)
|
||||
{
|
||||
return BlueprintGhostResolved{configurable
|
||||
return BlueprintGhostResolved{isConfigurableBuildingType(type)
|
||||
? BlueprintGhostAction::Transfer
|
||||
: BlueprintGhostAction::CompatibleOverlap,
|
||||
coinciding->id};
|
||||
coinciding->id, anchor, rotation};
|
||||
}
|
||||
|
||||
// Facing the other way. Placement may not re-orient it, so this is invalid --
|
||||
// except for the deliberate one-click gesture of a single-building blueprint,
|
||||
// where a transfer rotates nothing anyway and the target's facing is beside the
|
||||
// point (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
if (blueprintHoldsOneBuilding && configurable)
|
||||
{
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::Transfer, coinciding->id};
|
||||
}
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt};
|
||||
// Facing the other way, and placement may not re-orient it.
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
|
||||
anchor, rotation};
|
||||
}
|
||||
|
||||
// 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}; }
|
||||
if (!def)
|
||||
{
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::Invalid, std::nullopt,
|
||||
anchor, rotation};
|
||||
}
|
||||
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::Invalid, std::nullopt,
|
||||
anchor, rotation};
|
||||
}
|
||||
}
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::PlaceNew, std::nullopt};
|
||||
return BlueprintGhostResolved{BlueprintGhostAction::PlaceNew, std::nullopt,
|
||||
anchor, rotation};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user