Use std::optional instead of sentinel values for absent data
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "BuildingId.h"
|
||||
|
||||
// Deliver-scrap behavior (one half of the old SalvageBehaviorComponent). Scored
|
||||
@@ -7,6 +9,6 @@
|
||||
// SalvagerSystem performs the actual delivery.
|
||||
struct DeliverScrapBehavior
|
||||
{
|
||||
BuildingId deliveryBay = kInvalidBuildingId;
|
||||
float score = 0.0f;
|
||||
std::optional<BuildingId> deliveryBay; // nullopt until a bay is assigned
|
||||
float score = 0.0f;
|
||||
};
|
||||
|
||||
@@ -88,8 +88,8 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem&
|
||||
m_admin.forEach<DeliverScrapBehavior, PositionComponent>(
|
||||
[&](entt::entity ship, const DeliverScrapBehavior& deliver, const PositionComponent& pos)
|
||||
{
|
||||
if (deliver.deliveryBay == kInvalidBuildingId) { return; }
|
||||
const Building* bay = buildings.findBuilding(deliver.deliveryBay);
|
||||
if (!deliver.deliveryBay.has_value()) { return; }
|
||||
const Building* bay = buildings.findBuilding(*deliver.deliveryBay);
|
||||
if (!bay) { return; }
|
||||
|
||||
const QVector2D bayCenter(bay->anchor.x() + bay->footprint.width() / 2.0f,
|
||||
@@ -100,7 +100,7 @@ void SalvagerSystem::tick(Tick currentTick, ScrapSystem& scraps, BuildingSystem&
|
||||
if (!m_admin.hasAll<CargoComponent>(ship)) { return; }
|
||||
CargoComponent& cargo = m_admin.get<CargoComponent>(ship);
|
||||
if (cargo.current <= 0) { return; }
|
||||
if (buildings.deliverScrapToSalvageBay(deliver.deliveryBay))
|
||||
if (buildings.deliverScrapToSalvageBay(*deliver.deliveryBay))
|
||||
{
|
||||
--cargo.current;
|
||||
}
|
||||
|
||||
@@ -398,8 +398,7 @@ entt::entity ShipSystem::spawn(const std::string& schematicId,
|
||||
maxCollRange * static_cast<float>(m_config.world.orbitFactor);
|
||||
m_admin.addComponent<SalvageScrapBehavior>(entity, salvage);
|
||||
|
||||
DeliverScrapBehavior deliver;
|
||||
deliver.deliveryBay = kInvalidBuildingId;
|
||||
DeliverScrapBehavior deliver; // deliveryBay starts unassigned (nullopt)
|
||||
m_admin.addComponent<DeliverScrapBehavior>(entity, deliver);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ void DeliverScrapEvaluator::evaluate(EntityAdmin& admin, const BuildingSystem& b
|
||||
}
|
||||
|
||||
// Assign nearest SalvageBay if not yet assigned.
|
||||
if (deliver.deliveryBay == kInvalidBuildingId)
|
||||
if (!deliver.deliveryBay.has_value())
|
||||
{
|
||||
const Building* bay =
|
||||
buildings.findNearestBuilding(pos.value, BuildingType::SalvageBay);
|
||||
|
||||
@@ -24,9 +24,9 @@ void DeliverScrapExecutor::execute(EntityAdmin& admin, const BuildingSystem& bui
|
||||
if (selected.winner != BehaviorKind::DeliverScrap) { return; }
|
||||
|
||||
QVector2D dest = pos.value;
|
||||
if (deliver.deliveryBay != kInvalidBuildingId)
|
||||
if (deliver.deliveryBay.has_value())
|
||||
{
|
||||
const Building* bay = buildings.findBuilding(deliver.deliveryBay);
|
||||
const Building* bay = buildings.findBuilding(*deliver.deliveryBay);
|
||||
if (bay)
|
||||
{
|
||||
dest = QVector2D(bay->anchor.x() + bay->footprint.width() / 2.0f,
|
||||
|
||||
@@ -346,7 +346,7 @@ std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe
|
||||
// Placement
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
Rotation rotation, Tick currentTick)
|
||||
{
|
||||
const BuildingDef* def = findBuildingDef(type);
|
||||
@@ -356,7 +356,7 @@ BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
|
||||
// Reject placements that fall outside the world (REQ-BLD-PLACE-VALID).
|
||||
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const BuildingId id = m_allocateBuildingId();
|
||||
|
||||
@@ -43,13 +43,13 @@ public:
|
||||
std::mt19937& rng);
|
||||
|
||||
// -- Placement / demolish ------------------------------------------------
|
||||
// Returns the new entity id, or kInvalidBuildingId if the placement falls
|
||||
// outside the world bounds (vertical extent and asteroid left edge). Belt
|
||||
// and Splitter register with BeltSystem directly; other types enter the
|
||||
// construction queue. Terrain type (A vs S) is NOT checked here so that
|
||||
// tests can stage arbitrary layouts; the player-facing entry point
|
||||
// Returns the new entity id, or nullopt if the placement falls outside the
|
||||
// world bounds (vertical extent and asteroid left edge). Belt and Splitter
|
||||
// register with BeltSystem directly; other types enter the construction
|
||||
// queue. Terrain type (A vs S) is NOT checked here so that tests can stage
|
||||
// arbitrary layouts; the player-facing entry point
|
||||
// (Simulation::tryPlaceBuilding) enforces the full rule via isPlacementValid.
|
||||
BuildingId place(BuildingType type, QPoint anchor, Rotation rotation,
|
||||
std::optional<BuildingId> place(BuildingType type, QPoint anchor, Rotation rotation,
|
||||
Tick currentTick);
|
||||
|
||||
// Returns true if the placement satisfies REQ-BLD-PLACE-VALID terrain and
|
||||
|
||||
@@ -75,28 +75,28 @@ struct PlaceBuildingCommand : Command
|
||||
struct DemolishCommand : Command
|
||||
{
|
||||
DemolishCommand() : Command(CommandKind::Demolish) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
std::optional<BuildingId> id;
|
||||
};
|
||||
|
||||
struct RotateInPlaceCommand : Command
|
||||
{
|
||||
RotateInPlaceCommand() : Command(CommandKind::RotateInPlace) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
Rotation newRotation = Rotation::East;
|
||||
std::optional<BuildingId> id;
|
||||
Rotation newRotation = Rotation::East;
|
||||
};
|
||||
|
||||
struct SetRecipeCommand : Command
|
||||
{
|
||||
SetRecipeCommand() : Command(CommandKind::SetRecipe) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
std::string recipeId;
|
||||
std::optional<BuildingId> id;
|
||||
std::string recipeId;
|
||||
};
|
||||
|
||||
struct SetShipLayoutCommand : Command
|
||||
{
|
||||
SetShipLayoutCommand() : Command(CommandKind::SetShipLayout) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
ShipLayoutConfig layout;
|
||||
std::optional<BuildingId> id;
|
||||
ShipLayoutConfig layout;
|
||||
};
|
||||
|
||||
// Splitter filters for a queued / under-construction Splitter site (configured by
|
||||
@@ -104,8 +104,8 @@ struct SetShipLayoutCommand : Command
|
||||
struct SetSiteSplitterFiltersCommand : Command
|
||||
{
|
||||
SetSiteSplitterFiltersCommand() : Command(CommandKind::SetSiteSplitterFilters) {}
|
||||
BuildingId id = kInvalidBuildingId;
|
||||
std::vector<ItemType> filterA;
|
||||
std::optional<BuildingId> id;
|
||||
std::vector<ItemType> filterA;
|
||||
std::vector<ItemType> filterB;
|
||||
};
|
||||
|
||||
|
||||
@@ -129,24 +129,24 @@ std::string serializeCommand(const Command& command)
|
||||
break;
|
||||
}
|
||||
case CommandKind::Demolish:
|
||||
out << "demolish " << static_cast<const DemolishCommand&>(command).id;
|
||||
out << "demolish " << static_cast<const DemolishCommand&>(command).id.value();
|
||||
break;
|
||||
case CommandKind::RotateInPlace:
|
||||
{
|
||||
const RotateInPlaceCommand& c = static_cast<const RotateInPlaceCommand&>(command);
|
||||
out << "rotate " << c.id << ' ' << rotationToChar(c.newRotation);
|
||||
out << "rotate " << c.id.value() << ' ' << rotationToChar(c.newRotation);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetRecipe:
|
||||
{
|
||||
const SetRecipeCommand& c = static_cast<const SetRecipeCommand&>(command);
|
||||
out << "setrecipe " << c.id << ' ' << c.recipeId;
|
||||
out << "setrecipe " << c.id.value() << ' ' << c.recipeId;
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetShipLayout:
|
||||
{
|
||||
const SetShipLayoutCommand& c = static_cast<const SetShipLayoutCommand&>(command);
|
||||
out << "setlayout " << c.id << ' ';
|
||||
out << "setlayout " << c.id.value() << ' ';
|
||||
appendLayout(out, c.layout);
|
||||
break;
|
||||
}
|
||||
@@ -154,7 +154,7 @@ std::string serializeCommand(const Command& command)
|
||||
{
|
||||
const SetSiteSplitterFiltersCommand& c =
|
||||
static_cast<const SetSiteSplitterFiltersCommand&>(command);
|
||||
out << "sitefilters " << c.id << ' ';
|
||||
out << "sitefilters " << c.id.value() << ' ';
|
||||
appendFilters(out, c.filterA, c.filterB);
|
||||
break;
|
||||
}
|
||||
@@ -242,27 +242,35 @@ std::shared_ptr<Command> parseCommand(const std::string& tokens)
|
||||
if (verb == "demolish")
|
||||
{
|
||||
std::shared_ptr<DemolishCommand> c = std::make_shared<DemolishCommand>();
|
||||
if (!(in >> c->id)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id)) { return nullptr; }
|
||||
c->id = id;
|
||||
return c;
|
||||
}
|
||||
if (verb == "rotate")
|
||||
{
|
||||
std::shared_ptr<RotateInPlaceCommand> c = std::make_shared<RotateInPlaceCommand>();
|
||||
std::string rotToken;
|
||||
if (!(in >> c->id >> rotToken)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id >> rotToken)) { return nullptr; }
|
||||
c->id = id;
|
||||
c->newRotation = rotationFromString(rotToken);
|
||||
return c;
|
||||
}
|
||||
if (verb == "setrecipe")
|
||||
{
|
||||
std::shared_ptr<SetRecipeCommand> c = std::make_shared<SetRecipeCommand>();
|
||||
if (!(in >> c->id >> c->recipeId)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id >> c->recipeId)) { return nullptr; }
|
||||
c->id = id;
|
||||
return c;
|
||||
}
|
||||
if (verb == "setlayout")
|
||||
{
|
||||
std::shared_ptr<SetShipLayoutCommand> c = std::make_shared<SetShipLayoutCommand>();
|
||||
if (!(in >> c->id)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id)) { return nullptr; }
|
||||
c->id = id;
|
||||
c->layout = parseLayout(in, ok);
|
||||
if (!ok) { return nullptr; }
|
||||
return c;
|
||||
@@ -271,7 +279,9 @@ std::shared_ptr<Command> parseCommand(const std::string& tokens)
|
||||
{
|
||||
std::shared_ptr<SetSiteSplitterFiltersCommand> c =
|
||||
std::make_shared<SetSiteSplitterFiltersCommand>();
|
||||
if (!(in >> c->id)) { return nullptr; }
|
||||
BuildingId id = 0;
|
||||
if (!(in >> id)) { return nullptr; }
|
||||
c->id = id;
|
||||
parseFilters(in, c->filterA, c->filterB, ok);
|
||||
if (!ok) { return nullptr; }
|
||||
return c;
|
||||
|
||||
@@ -40,7 +40,6 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
|
||||
, m_nextBuildingId(1)
|
||||
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
|
||||
, m_gameOver(false)
|
||||
, m_hqBuildingId(kInvalidBuildingId)
|
||||
, m_hqProxyEntity(entt::null)
|
||||
, m_playerStation1Entity(entt::null)
|
||||
, m_playerStation2Entity(entt::null)
|
||||
@@ -137,7 +136,7 @@ void Simulation::reset(unsigned int seed)
|
||||
m_gameOver = false;
|
||||
m_isWon = false;
|
||||
m_artifactCount = 0;
|
||||
m_hqBuildingId = kInvalidBuildingId;
|
||||
m_hqBuildingId = std::nullopt;
|
||||
m_hqProxyEntity = entt::null;
|
||||
m_playerStation1Entity = entt::null;
|
||||
m_playerStation2Entity = entt::null;
|
||||
@@ -217,11 +216,12 @@ void Simulation::apply(const Command& command)
|
||||
case CommandKind::PlaceBuilding:
|
||||
{
|
||||
const PlaceBuildingCommand& c = static_cast<const PlaceBuildingCommand&>(command);
|
||||
const BuildingId id = tryPlaceBuilding(c.type, c.anchor, c.rotation);
|
||||
if (id == kInvalidBuildingId)
|
||||
const std::optional<BuildingId> placed = tryPlaceBuilding(c.type, c.anchor, c.rotation);
|
||||
if (!placed.has_value())
|
||||
{
|
||||
break;
|
||||
}
|
||||
const BuildingId id = *placed;
|
||||
if (c.recipeId.has_value())
|
||||
{
|
||||
m_buildingSystem->setRecipe(id, *c.recipeId);
|
||||
@@ -237,31 +237,31 @@ void Simulation::apply(const Command& command)
|
||||
break;
|
||||
}
|
||||
case CommandKind::Demolish:
|
||||
demolish(static_cast<const DemolishCommand&>(command).id);
|
||||
demolish(*static_cast<const DemolishCommand&>(command).id);
|
||||
break;
|
||||
case CommandKind::RotateInPlace:
|
||||
{
|
||||
const RotateInPlaceCommand& c = static_cast<const RotateInPlaceCommand&>(command);
|
||||
m_buildingSystem->rotateInPlace(c.id, c.newRotation);
|
||||
m_buildingSystem->rotateInPlace(*c.id, c.newRotation);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetRecipe:
|
||||
{
|
||||
const SetRecipeCommand& c = static_cast<const SetRecipeCommand&>(command);
|
||||
m_buildingSystem->setRecipe(c.id, c.recipeId);
|
||||
m_buildingSystem->setRecipe(*c.id, c.recipeId);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetShipLayout:
|
||||
{
|
||||
const SetShipLayoutCommand& c = static_cast<const SetShipLayoutCommand&>(command);
|
||||
m_buildingSystem->setShipLayout(c.id, c.layout);
|
||||
m_buildingSystem->setShipLayout(*c.id, c.layout);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetSiteSplitterFilters:
|
||||
{
|
||||
const SetSiteSplitterFiltersCommand& c =
|
||||
static_cast<const SetSiteSplitterFiltersCommand&>(command);
|
||||
m_buildingSystem->setSiteSplitterFilters(c.id, c.filterA, c.filterB);
|
||||
m_buildingSystem->setSiteSplitterFilters(*c.id, c.filterA, c.filterB);
|
||||
break;
|
||||
}
|
||||
case CommandKind::SetSplitterFilters:
|
||||
@@ -1193,11 +1193,11 @@ bool Simulation::isModuleSchematicUnlocked(const std::string& moduleId) const
|
||||
return it->second.unlocked;
|
||||
}
|
||||
|
||||
BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
||||
std::optional<BuildingId> Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
|
||||
{
|
||||
if (!m_buildingSystem->isPlacementValid(type, anchor, rotation))
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int cost = 0;
|
||||
@@ -1211,7 +1211,7 @@ BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotati
|
||||
}
|
||||
if (m_buildingBlocksStock < cost)
|
||||
{
|
||||
return kInvalidBuildingId;
|
||||
return std::nullopt;
|
||||
}
|
||||
m_buildingBlocksStock -= cost;
|
||||
return m_buildingSystem->place(type, anchor, rotation, m_currentTick);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -131,8 +132,8 @@ private:
|
||||
// Reached during play exclusively via apply(); never called by UI/app code.
|
||||
|
||||
// Checks affordability, deducts building blocks, and places the building.
|
||||
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
|
||||
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
// Returns the new entity id, or nullopt if blocks are insufficient.
|
||||
std::optional<BuildingId> tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
|
||||
|
||||
// Demolishes the building with the given id and refunds building blocks.
|
||||
void demolish(BuildingId id);
|
||||
@@ -182,7 +183,7 @@ private:
|
||||
int m_artifactCount = 0;
|
||||
|
||||
// Pre-placed structure IDs.
|
||||
BuildingId m_hqBuildingId; // Building id (for belt integration)
|
||||
std::optional<BuildingId> m_hqBuildingId; // Building id (for belt integration)
|
||||
entt::entity m_hqProxyEntity; // ECS entity (HP, targeting)
|
||||
entt::entity m_playerStation1Entity;
|
||||
entt::entity m_playerStation2Entity;
|
||||
|
||||
Reference in New Issue
Block a user