Clicking empty ground selected every building in the 3x3 neighbourhood, and a deconstruct click queued all of them -- demolishing any construction site among them outright, since sites skip the queue. Two allowances stacked. A click that hits nothing resolves as a box drag, and a box that never moved becomes the unit square of the tile the button went down on (GameWorldView::getBoxWorldRect), so its edges lie exactly on tile boundaries. boxCoversTile then counted a shared boundary as coverage -- written for the sub-tile drag rectangles it was introduced for, where "grazes the edge" is the generous reading, but the click box grazes all four of its neighbours, and their diagonals through the corners. Coverage now has to have area: the far-edge comparisons are strict, so a box ending on a boundary does not reach past it. Drags stop grabbing the one-tile ring around themselves too, and station bodies come along, sharing the rule through actorsInBox. The existing box tests use rectangles that sit inside a tile, which is why none of them saw this; the new one queries tile-aligned boxes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
38 lines
1.8 KiB
C
38 lines
1.8 KiB
C
#pragma once
|
|
|
|
#include <QPoint>
|
|
#include <QRectF>
|
|
#include <QVector2D>
|
|
|
|
// The coverage rules of a selection box (REQ-UI-MULTI-SELECT, REQ-BLD-DECONSTRUCT-BOX).
|
|
//
|
|
// The box is a rectangle in world coordinates — tiles as the unit, but fractional,
|
|
// because the drag follows the mouse and is not snapped to the tile grid. The two
|
|
// rules below are the whole of what "covered by the box" means; they live here so the
|
|
// building query and the entity queries answer it identically.
|
|
//
|
|
// Both callers pass a normalized rectangle: neither rule normalizes on its own.
|
|
|
|
// Whether the box overlaps the unit square of `tile` — the rule for anything that
|
|
// occupies whole tiles (buildings, construction sites, defence station bodies). The
|
|
// overlap must have area: a box that ends exactly on a tile boundary does not reach
|
|
// into the tile beyond it. That matters because a click resolves as the unit square
|
|
// of the tile it hit (GameWorldView::getBoxWorldRect), so its edges lie exactly on
|
|
// tile boundaries — with the touching edges counted as coverage, such a box would
|
|
// cover the whole 3x3 neighbourhood instead of the one tile clicked.
|
|
inline bool boxCoversTile(const QRectF& worldBox, QPoint tile)
|
|
{
|
|
return worldBox.left() < static_cast<qreal>(tile.x()) + 1.0
|
|
&& worldBox.right() > static_cast<qreal>(tile.x())
|
|
&& worldBox.top() < static_cast<qreal>(tile.y()) + 1.0
|
|
&& worldBox.bottom() > static_cast<qreal>(tile.y());
|
|
}
|
|
|
|
// Whether the box contains `worldPos` — the rule for anything that has a position
|
|
// rather than a footprint (ships, debris). Their centre is what the box must enclose,
|
|
// so that what the rectangle visibly holds is what the drag selects.
|
|
inline bool boxCoversPoint(const QRectF& worldBox, QVector2D worldPos)
|
|
{
|
|
return worldBox.contains(QPointF(worldPos.x(), worldPos.y()));
|
|
}
|