Add demolish box-drag interaction

This commit is contained in:
2026-07-08 21:04:59 +02:00
parent 808e0c6a7b
commit f11db0c072
3 changed files with 84 additions and 47 deletions

View File

@@ -570,6 +570,41 @@ BuildingId GameWorldView::siteAtTile(QPoint tile) const
}
std::vector<BuildingId> GameWorldView::buildingsInBox(QPoint cornerA, QPoint cornerB) const
{
const int x0 = std::min(cornerA.x(), cornerB.x());
const int y0 = std::min(cornerA.y(), cornerB.y());
const int x1 = std::max(cornerA.x(), cornerB.x());
const int y1 = std::max(cornerA.y(), cornerB.y());
std::vector<BuildingId> ids;
for (const Building& b : m_sim->buildings().allBuildings())
{
for (const QPoint& cell : b.bodyCells)
{
if (cell.x() >= x0 && cell.x() <= x1
&& cell.y() >= y0 && cell.y() <= y1)
{
ids.push_back(b.id);
break;
}
}
}
for (const ConstructionSite& s : m_sim->buildings().allSites())
{
for (const QPoint& cell : s.bodyCells)
{
if (cell.x() >= x0 && cell.x() <= x1
&& cell.y() >= y0 && cell.y() <= y1)
{
ids.push_back(s.id);
break;
}
}
}
return ids;
}
std::optional<QVector2D> GameWorldView::entityPosition(entt::entity entity) const
{
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<PositionComponent>(entity))
@@ -1236,8 +1271,28 @@ void GameWorldView::drawOverlays(QPainter& painter)
}
}
// Demolish hover tint
if (m_demolishMode && m_demolishHoverBuildingId != kInvalidBuildingId)
// Demolish tint: while dragging a demolish box, tint every covered
// building/site (REQ-BLD-DEMOLISH-BOX); otherwise tint the hovered one.
if (m_demolishMode && m_boxSelecting)
{
for (BuildingId id : buildingsInBox(m_boxStartTile, m_boxCurrentTile))
{
const Building* b = m_sim->buildings().findBuilding(id);
if (b && b->type == BuildingType::Hq) { continue; }
const std::vector<QPoint>* cells = nullptr;
const ConstructionSite* s = nullptr;
if (b) { cells = &b->bodyCells; }
else if ((s = m_sim->buildings().findSite(id))) { cells = &s->bodyCells; }
if (cells)
{
for (const QPoint& cell : *cells)
{
painter.fillRect(tileRect(cell), m_visuals->overlays.demolishTint);
}
}
}
}
else if (m_demolishMode && m_demolishHoverBuildingId != kInvalidBuildingId)
{
const Building* b = m_sim->buildings().findBuilding(m_demolishHoverBuildingId);
if (b)
@@ -1531,24 +1586,11 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
}
else if (m_demolishMode)
{
BuildingId hovered = buildingAtTile(tile);
if (hovered == kInvalidBuildingId)
{
hovered = siteAtTile(tile);
}
if (hovered != kInvalidBuildingId)
{
const Building* b = m_sim->buildings().findBuilding(hovered);
const bool isProtected = b && b->type == BuildingType::Hq;
if (!isProtected)
{
std::shared_ptr<DemolishCommand> command =
std::make_shared<DemolishCommand>();
command->id = hovered;
enqueueCommand(command);
m_demolishHoverBuildingId = kInvalidBuildingId;
}
}
// Start a demolish box drag; a plain click resolves as a 1x1 box on
// release (REQ-BLD-DEMOLISH-CLICK, REQ-BLD-DEMOLISH-BOX).
m_boxSelecting = true;
m_boxStartTile = tile;
m_boxCurrentTile = tile;
}
else
{
@@ -1636,6 +1678,7 @@ void GameWorldView::mouseMoveEvent(QMouseEvent* event)
else if (m_demolishMode)
{
m_demolishHoverBuildingId = buildingAtTile(tile);
if (m_boxSelecting) { m_boxCurrentTile = tile; }
}
else if (m_boxSelecting)
{
@@ -1657,44 +1700,33 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
{
m_boxSelecting = false;
const int x0 = std::min(m_boxStartTile.x(), m_boxCurrentTile.x());
const int y0 = std::min(m_boxStartTile.y(), m_boxCurrentTile.y());
const int x1 = std::max(m_boxStartTile.x(), m_boxCurrentTile.x());
const int y1 = std::max(m_boxStartTile.y(), m_boxCurrentTile.y());
const std::vector<BuildingId> boxIds =
buildingsInBox(m_boxStartTile, m_boxCurrentTile);
std::vector<BuildingId> boxSel;
for (const Building& b : m_sim->buildings().allBuildings())
if (m_demolishMode)
{
for (const QPoint& cell : b.bodyCells)
// Demolish every covered building/site; the HQ is protected
// (REQ-BLD-DEMOLISH, REQ-BLD-DEMOLISH-BOX).
for (BuildingId id : boxIds)
{
if (cell.x() >= x0 && cell.x() <= x1
&& cell.y() >= y0 && cell.y() <= y1)
{
boxSel.push_back(b.id);
break;
}
}
}
for (const ConstructionSite& s : m_sim->buildings().allSites())
{
for (const QPoint& cell : s.bodyCells)
{
if (cell.x() >= x0 && cell.x() <= x1
&& cell.y() >= y0 && cell.y() <= y1)
{
boxSel.push_back(s.id);
break;
}
const Building* b = m_sim->buildings().findBuilding(id);
if (b && b->type == BuildingType::Hq) { continue; }
std::shared_ptr<DemolishCommand> command =
std::make_shared<DemolishCommand>();
command->id = id;
enqueueCommand(command);
}
m_demolishHoverBuildingId = kInvalidBuildingId;
return;
}
if (!(event->modifiers() & Qt::ControlModifier))
{
m_selectedBuildingIds = boxSel;
m_selectedBuildingIds = boxIds;
}
else
{
for (BuildingId id : boxSel)
for (BuildingId id : boxIds)
{
bool found = false;
for (BuildingId sel : m_selectedBuildingIds)

View File

@@ -143,6 +143,9 @@ private:
const BuildingDef* findBuildingDef(BuildingType type) const;
BuildingId buildingAtTile(QPoint tile) const;
BuildingId siteAtTile(QPoint tile) const;
// Ids of all buildings and construction sites whose footprint intersects
// the tile box spanned by the two (unordered) corner tiles.
std::vector<BuildingId> buildingsInBox(QPoint cornerA, QPoint cornerB) const;
QVector2D widgetToWorld(QPoint widgetPt) const;
void drawPortGlyph(QPainter& painter, QPoint bodyTile,