Add demolish box-drag interaction
This commit is contained in:
@@ -109,6 +109,8 @@ Modules in `modules.toml` define a `surface_mask` — a list of strings that des
|
|||||||
- REQ-BLD-BELT-DRAG: For belts, the player can click and drag across multiple tiles to place a construction site on each tile in one gesture.
|
- REQ-BLD-BELT-DRAG: For belts, the player can click and drag across multiple tiles to place a construction site on each tile in one gesture.
|
||||||
- REQ-BLD-TUNNEL-AUTO-SWITCH: After the player successfully places a Tunnel Entry construction site, builder mode automatically switches to Tunnel Exit (and vice versa), preserving the current ghost rotation. This makes it easy to immediately place the paired end without manually selecting the complementary type.
|
- REQ-BLD-TUNNEL-AUTO-SWITCH: After the player successfully places a Tunnel Entry construction site, builder mode automatically switches to Tunnel Exit (and vice versa), preserving the current ghost rotation. This makes it easy to immediately place the paired end without manually selecting the complementary type.
|
||||||
- REQ-BLD-DEMOLISH: The player can demolish a placed factory building. Demolition returns `world.toml [world].refund_percentage` percent of the original building block cost (default 75%) to the global stock. Exception: if the building is still in the construction queue (not yet fully built, including the one currently being constructed), it is removed from the queue and the **full** building block cost is refunded. The HQ and player defence stations cannot be demolished.
|
- REQ-BLD-DEMOLISH: The player can demolish a placed factory building. Demolition returns `world.toml [world].refund_percentage` percent of the original building block cost (default 75%) to the global stock. Exception: if the building is still in the construction queue (not yet fully built, including the one currently being constructed), it is removed from the queue and the **full** building block cost is refunded. The HQ and player defence stations cannot be demolished.
|
||||||
|
- REQ-BLD-DEMOLISH-CLICK: While in demolish mode (REQ-UI-HOTKEYS, REQ-UI-DEMOLISH-BUTTON), left-clicking a placed factory building or construction site in the game world demolishes it, following the refund rules of REQ-BLD-DEMOLISH — the partial refund for built buildings and the full refund for still-queued construction sites. Clicking a building that cannot be demolished (the HQ or a player defence station, per REQ-BLD-DEMOLISH), or clicking empty world space, has no effect. Demolish mode stays active after a demolition so the player can demolish further buildings without re-entering the mode; it is exited via the Q toggle (REQ-UI-HOTKEYS) or the Demolish button (REQ-UI-DEMOLISH-BUTTON).
|
||||||
|
- REQ-BLD-DEMOLISH-BOX: While in demolish mode (REQ-UI-HOTKEYS, REQ-UI-DEMOLISH-BUTTON), the player can click and drag a selection box in the game world. A selection rectangle is drawn while dragging, using the same box-drag gesture and coverage semantics as the multi-select box (REQ-UI-MULTI-SELECT). On mouse up, every placed factory building and construction site covered by the box is demolished, each following the refund rules of REQ-BLD-DEMOLISH — the partial refund for built buildings and the full refund for still-queued construction sites. Buildings that cannot be demolished (the HQ and player defence stations, per REQ-BLD-DEMOLISH) are excluded from the box demolition; ships and defence stations are never affected.
|
||||||
- REQ-BLD-SITE-CONFIG: A construction site — a building that has been placed but is still queued or under construction (REQ-BLD-QUEUE) — can be selected and configured exactly like the equivalent operational building, before it finishes building. Whatever configuration the building type supports is available on the site: the recipe for a Miner or Assembler (REQ-UI-SELECT-BUTTON), the produced-ship schematic and its module layout for a Shipyard (REQ-UI-SELECT-BUTTON, REQ-MOD-UI-PREVIEW, REQ-MOD-UI-DIALOG), and the output filters for a Splitter (REQ-BLD-SPLITTER) — all set through the same Selected Building Panel controls (REQ-UI-CONFIG-INLINE). Only currently unlocked recipes and schematics are offered, exactly as for operational buildings (REQ-LOCK-UI-RECIPE, REQ-LOCK-UI-SCHEMATIC, REQ-LOCK-UI-SPLITTER). The configuration is stored on the construction site and carries over unchanged when construction completes, so the building becomes operational already configured. A construction site has no input/output buffers and runs no production cycle, so the buffer and production-progress portions of the panel (REQ-UI-SINGLE-SELECTION, REQ-UI-PRODUCTION-PROGRESS) are not shown for it; only its construction progress (REQ-UI-CONSTRUCTION-PROGRESS) and its configuration controls appear. (Blueprint placement already applies a stored recipe or schematic to a construction site on placement per REQ-UI-BLUEPRINT-PLACE; this requirement additionally lets the player set or change that configuration directly on an existing site.)
|
- REQ-BLD-SITE-CONFIG: A construction site — a building that has been placed but is still queued or under construction (REQ-BLD-QUEUE) — can be selected and configured exactly like the equivalent operational building, before it finishes building. Whatever configuration the building type supports is available on the site: the recipe for a Miner or Assembler (REQ-UI-SELECT-BUTTON), the produced-ship schematic and its module layout for a Shipyard (REQ-UI-SELECT-BUTTON, REQ-MOD-UI-PREVIEW, REQ-MOD-UI-DIALOG), and the output filters for a Splitter (REQ-BLD-SPLITTER) — all set through the same Selected Building Panel controls (REQ-UI-CONFIG-INLINE). Only currently unlocked recipes and schematics are offered, exactly as for operational buildings (REQ-LOCK-UI-RECIPE, REQ-LOCK-UI-SCHEMATIC, REQ-LOCK-UI-SPLITTER). The configuration is stored on the construction site and carries over unchanged when construction completes, so the building becomes operational already configured. A construction site has no input/output buffers and runs no production cycle, so the buffer and production-progress portions of the panel (REQ-UI-SINGLE-SELECTION, REQ-UI-PRODUCTION-PROGRESS) are not shown for it; only its construction progress (REQ-UI-CONSTRUCTION-PROGRESS) and its configuration controls appear. (Blueprint placement already applies a stored recipe or schematic to a construction site on placement per REQ-UI-BLUEPRINT-PLACE; this requirement additionally lets the player set or change that configuration directly on an existing site.)
|
||||||
|
|
||||||
## Building Types
|
## Building Types
|
||||||
|
|||||||
@@ -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
|
std::optional<QVector2D> GameWorldView::entityPosition(entt::entity entity) const
|
||||||
{
|
{
|
||||||
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<PositionComponent>(entity))
|
if (!m_sim->admin().isValid(entity) || !m_sim->admin().hasAll<PositionComponent>(entity))
|
||||||
@@ -1236,8 +1271,28 @@ void GameWorldView::drawOverlays(QPainter& painter)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Demolish hover tint
|
// Demolish tint: while dragging a demolish box, tint every covered
|
||||||
if (m_demolishMode && m_demolishHoverBuildingId != kInvalidBuildingId)
|
// 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);
|
const Building* b = m_sim->buildings().findBuilding(m_demolishHoverBuildingId);
|
||||||
if (b)
|
if (b)
|
||||||
@@ -1531,24 +1586,11 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
|
|||||||
}
|
}
|
||||||
else if (m_demolishMode)
|
else if (m_demolishMode)
|
||||||
{
|
{
|
||||||
BuildingId hovered = buildingAtTile(tile);
|
// Start a demolish box drag; a plain click resolves as a 1x1 box on
|
||||||
if (hovered == kInvalidBuildingId)
|
// release (REQ-BLD-DEMOLISH-CLICK, REQ-BLD-DEMOLISH-BOX).
|
||||||
{
|
m_boxSelecting = true;
|
||||||
hovered = siteAtTile(tile);
|
m_boxStartTile = tile;
|
||||||
}
|
m_boxCurrentTile = 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1636,6 +1678,7 @@ void GameWorldView::mouseMoveEvent(QMouseEvent* event)
|
|||||||
else if (m_demolishMode)
|
else if (m_demolishMode)
|
||||||
{
|
{
|
||||||
m_demolishHoverBuildingId = buildingAtTile(tile);
|
m_demolishHoverBuildingId = buildingAtTile(tile);
|
||||||
|
if (m_boxSelecting) { m_boxCurrentTile = tile; }
|
||||||
}
|
}
|
||||||
else if (m_boxSelecting)
|
else if (m_boxSelecting)
|
||||||
{
|
{
|
||||||
@@ -1657,44 +1700,33 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
|
|||||||
{
|
{
|
||||||
m_boxSelecting = false;
|
m_boxSelecting = false;
|
||||||
|
|
||||||
const int x0 = std::min(m_boxStartTile.x(), m_boxCurrentTile.x());
|
const std::vector<BuildingId> boxIds =
|
||||||
const int y0 = std::min(m_boxStartTile.y(), m_boxCurrentTile.y());
|
buildingsInBox(m_boxStartTile, m_boxCurrentTile);
|
||||||
const int x1 = std::max(m_boxStartTile.x(), m_boxCurrentTile.x());
|
|
||||||
const int y1 = std::max(m_boxStartTile.y(), m_boxCurrentTile.y());
|
|
||||||
|
|
||||||
std::vector<BuildingId> boxSel;
|
if (m_demolishMode)
|
||||||
for (const Building& b : m_sim->buildings().allBuildings())
|
|
||||||
{
|
{
|
||||||
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
|
const Building* b = m_sim->buildings().findBuilding(id);
|
||||||
&& cell.y() >= y0 && cell.y() <= y1)
|
if (b && b->type == BuildingType::Hq) { continue; }
|
||||||
{
|
std::shared_ptr<DemolishCommand> command =
|
||||||
boxSel.push_back(b.id);
|
std::make_shared<DemolishCommand>();
|
||||||
break;
|
command->id = id;
|
||||||
}
|
enqueueCommand(command);
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
m_demolishHoverBuildingId = kInvalidBuildingId;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(event->modifiers() & Qt::ControlModifier))
|
if (!(event->modifiers() & Qt::ControlModifier))
|
||||||
{
|
{
|
||||||
m_selectedBuildingIds = boxSel;
|
m_selectedBuildingIds = boxIds;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (BuildingId id : boxSel)
|
for (BuildingId id : boxIds)
|
||||||
{
|
{
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (BuildingId sel : m_selectedBuildingIds)
|
for (BuildingId sel : m_selectedBuildingIds)
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ private:
|
|||||||
const BuildingDef* findBuildingDef(BuildingType type) const;
|
const BuildingDef* findBuildingDef(BuildingType type) const;
|
||||||
BuildingId buildingAtTile(QPoint tile) const;
|
BuildingId buildingAtTile(QPoint tile) const;
|
||||||
BuildingId siteAtTile(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;
|
QVector2D widgetToWorld(QPoint widgetPt) const;
|
||||||
|
|
||||||
void drawPortGlyph(QPainter& painter, QPoint bodyTile,
|
void drawPortGlyph(QPainter& painter, QPoint bodyTile,
|
||||||
|
|||||||
Reference in New Issue
Block a user