Use std::optional instead of sentinel values for absent data

Replace sentinel values that signalled "no value" with std::optional
across simulation, UI, and balancing code:

- BuildingId references: DeliverScrapBehavior::deliveryBay,
  Simulation::m_hqBuildingId, the return types of BuildingSystem::place
  and Simulation::tryPlaceBuilding (previously kInvalidBuildingId on
  failure), GameWorldView building/site-at-tile lookups and demolish
  hover, SelectedBuildingPanel::m_singleBuildingId.
- Command id fields: the five id-carrying commands now hold
  optional<BuildingId>; CommandSerializer and Simulation::apply updated.
  The serialized replay format is unchanged.
- Index sentinels: BlueprintPanel::m_activeIndex,
  BalancingWindow::m_inspectedArenaIndex, ArenaSimulation winnerTeam,
  and the moduleIndex grid cells in ShipLayoutDialog/ShipLayoutPreview.
  ShipLayoutDialog::m_activeModuleIndex was a tri-state, so it is
  modelled as bool m_removeMode + optional<int>.

Building/ConstructionSite id defaults keep kInvalidBuildingId (identity,
not an absent reference); entt::null and config -1 domain values are
left as-is.

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-20 07:36:34 +02:00
parent b708e1b29d
commit 388d354e51
32 changed files with 228 additions and 209 deletions

View File

@@ -142,7 +142,6 @@ GameWorldView::GameWorldView(Simulation* sim, const GameConfig* config,
, m_ghostValid(false)
, m_dragging(false)
, m_demolishMode(false)
, m_demolishHoverBuildingId(kInvalidBuildingId)
, m_debugDraw(false)
, m_rng(std::random_device{}())
, m_boxSelecting(false)
@@ -621,34 +620,34 @@ bool GameWorldView::isValidPlacement(BuildingType type, QPoint anchor,
return true;
}
BuildingId GameWorldView::buildingAtTile(QPoint tile) const
std::optional<BuildingId> GameWorldView::buildingAtTile(QPoint tile) const
{
for (const Building& b : m_sim->getBuildings().getAllBuildings())
{
for (const QPoint& cell : b.bodyCells)
{
if (cell == tile)
{
return b.id;
if (cell == tile)
{
return b.id;
}
}
}
return kInvalidBuildingId;
return std::nullopt;
}
BuildingId GameWorldView::siteAtTile(QPoint tile) const
std::optional<BuildingId> GameWorldView::siteAtTile(QPoint tile) const
{
for (const ConstructionSite& s : m_sim->getBuildings().getAllSites())
{
for (const QPoint& cell : s.bodyCells)
{
if (cell == tile)
if (cell == tile)
{
return s.id;
return s.id;
}
}
}
return kInvalidBuildingId;
return std::nullopt;
}
@@ -1606,9 +1605,9 @@ void GameWorldView::drawOverlays(QPainter& painter)
}
}
}
else if (m_demolishMode && m_demolishHoverBuildingId != kInvalidBuildingId)
else if (m_demolishMode && m_demolishHoverBuildingId.has_value())
{
const Building* b = m_sim->getBuildings().findBuilding(m_demolishHoverBuildingId);
const Building* b = m_sim->getBuildings().findBuilding(*m_demolishHoverBuildingId);
if (b)
{
for (const QPoint& cell : b->bodyCells)
@@ -1961,9 +1960,9 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
// Shift + right-click copies a building's settings, but only in the
// default selection mode (REQ-BLD-COPY-CONFIG).
const QPoint tile = widgetToTile(event->pos());
BuildingId id = buildingAtTile(tile);
if (id == kInvalidBuildingId) { id = siteAtTile(tile); }
if (id != kInvalidBuildingId) { copyConfigFrom(id); }
std::optional<BuildingId> id = buildingAtTile(tile);
if (!id.has_value()) { id = siteAtTile(tile); }
if (id.has_value()) { copyConfigFrom(*id); }
}
}
return;
@@ -2004,11 +2003,11 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
// selection. Only active in the default selection mode.
if ((event->modifiers() & Qt::ShiftModifier) && m_copiedConfig.has_value())
{
BuildingId id = buildingAtTile(tile);
if (id == kInvalidBuildingId) { id = siteAtTile(tile); }
if (id != kInvalidBuildingId)
std::optional<BuildingId> id = buildingAtTile(tile);
if (!id.has_value()) { id = siteAtTile(tile); }
if (id.has_value())
{
pasteConfigTo(id);
pasteConfigTo(*id);
return;
}
}
@@ -2036,13 +2035,14 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
std::make_shared<EntitySelectedEvent>(std::nullopt));
}
BuildingId id = buildingAtTile(tile);
if (id == kInvalidBuildingId)
std::optional<BuildingId> hit = buildingAtTile(tile);
if (!hit.has_value())
{
id = siteAtTile(tile);
hit = siteAtTile(tile);
}
if (id != kInvalidBuildingId)
if (hit.has_value())
{
const BuildingId id = *hit;
// A building/construction site outranks scrap (REQ-UI-SCRAP-CLICK-SELECT).
clearScrapSelection();
if (event->modifiers() & Qt::ControlModifier)
@@ -2170,7 +2170,7 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
command->id = id;
enqueueCommand(command);
}
m_demolishHoverBuildingId = kInvalidBuildingId;
m_demolishHoverBuildingId = std::nullopt;
return;
}
@@ -2255,7 +2255,7 @@ void GameWorldView::toggleDemolishMode()
if (m_demolishMode)
{
m_demolishMode = false;
m_demolishHoverBuildingId = kInvalidBuildingId;
m_demolishHoverBuildingId = std::nullopt;
}
else
{
@@ -2451,7 +2451,7 @@ void GameWorldView::resetForNewGame()
m_ghostRotation = Rotation::East;
m_ghostValid = false;
m_demolishMode = false;
m_demolishHoverBuildingId = kInvalidBuildingId;
m_demolishHoverBuildingId = std::nullopt;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<DemolishModeChangedEvent>(false));
m_selectedBuildingIds.clear();