Snap belt-drag end tile to a building's input edge

When a belt drag ends with the cursor over a non-belt building or construction
site, the end tile now snaps to the input-capable adjacent tile closest to the
cursor and points into the target, instead of showing an invalid ghost on the
occupied tile.

Adds BuildingSystem::getInputPorts(id) (reusing computeInputPorts, refactored
into a bodyCells+outputPorts core) as the single source of truth for a target's
input-capable adjacent tiles; works for both operational buildings (stored
inputPorts) and construction sites (mask-derived). GameWorldView tracks the
cursor world position and, in recomputeBeltDragPath, picks the nearest input
port and overrides the end tile's rotation. Ghost preview, resolve, and apply
consume the snapped path unchanged.

Implements REQ-BLD-BELT-DRAG snapping-to-a-building.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc
This commit is contained in:
2026-07-21 16:19:59 +02:00
parent 77519826b2
commit 56c691abfe
5 changed files with 208 additions and 4 deletions

View File

@@ -267,16 +267,23 @@ void BuildingSystem::initSalvageBayBuffer(Building& b) const
}
std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
{
return computeInputPorts(b.bodyCells, b.outputPorts);
}
std::vector<Port> BuildingSystem::computeInputPorts(
const std::vector<QPoint>& bodyCells,
const std::vector<Port>& outputPorts) const
{
// Build lookup sets for quick membership checks.
std::set<std::pair<int, int>> bodySet;
for (const QPoint& cell : b.bodyCells)
for (const QPoint& cell : bodyCells)
{
bodySet.insert({cell.x(), cell.y()});
}
std::set<std::pair<int, int>> outputPortTiles;
for (const Port& port : b.outputPorts)
for (const Port& port : outputPorts)
{
outputPortTiles.insert({port.tile.x(), port.tile.y()});
}
@@ -294,7 +301,7 @@ std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
std::set<std::pair<int, int>> seen;
std::vector<Port> inputPorts;
for (const QPoint& cell : b.bodyCells)
for (const QPoint& cell : bodyCells)
{
for (int i = 0; i < 4; ++i)
{
@@ -317,6 +324,30 @@ std::vector<Port> BuildingSystem::computeInputPorts(const Building& b) const
return inputPorts;
}
std::vector<Port> BuildingSystem::getInputPorts(BuildingId id) const
{
if (const Building* building = findBuilding(id))
{
return building->inputPorts;
}
if (const ConstructionSite* site = findSite(id))
{
// A site stores no ports; derive its output ports from the mask (absolute)
// and run the same input-edge scan (REQ-BLD-BELT-DRAG, REQ-MAT-INPUT-PORTS).
const BuildingDef* def = findBuildingDef(site->type);
if (def == nullptr) { return {}; }
const ParsedSurfaceMask mask = parseSurfaceMask(def->surfaceMask, site->rotation);
std::vector<Port> outputPortsAbsolute;
outputPortsAbsolute.reserve(mask.outputPorts.size());
for (const Port& port : mask.outputPorts)
{
outputPortsAbsolute.push_back(Port{ site->anchor + port.tile, port.direction });
}
return computeInputPorts(site->bodyCells, outputPortsAbsolute);
}
return {};
}
std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe)
{
std::vector<const RecipeOutput*> eligible;