151 lines
4.8 KiB
C++
151 lines
4.8 KiB
C++
#pragma once
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
|
|
#include "BuildingId.h"
|
|
#include "BuildingType.h"
|
|
#include "ItemType.h"
|
|
#include "Rotation.h"
|
|
#include "ShipLayout.h"
|
|
|
|
class GameConfig;
|
|
|
|
// Player intent, resolved to domain ids / tile coordinates and serializable, that
|
|
// mutates the simulation. Every sim mutation during play flows through a Command
|
|
// applied at the single Simulation::apply chokepoint, so it can be recorded and
|
|
// replayed (see docs/replay_design.md).
|
|
//
|
|
// Commands form a closed set dispatched by kind. They reference stable,
|
|
// deterministic ids (BuildingId, tile coordinates, choice indices) — never raw
|
|
// entt handles — so a recorded command resolves to the same target on replay.
|
|
|
|
enum class CommandKind
|
|
{
|
|
PlaceBuilding,
|
|
Demolish,
|
|
RotateInPlace,
|
|
SetRecipe,
|
|
SetShipLayout,
|
|
SetSiteSplitterFilters,
|
|
SetSplitterFilters,
|
|
ClearBeltTiles,
|
|
ApplySchematicChoice,
|
|
ExpandAsteroid,
|
|
Reset
|
|
};
|
|
|
|
struct Command
|
|
{
|
|
explicit Command(CommandKind kind) : kind(kind) {}
|
|
virtual ~Command() = default;
|
|
|
|
CommandKind kind;
|
|
// Source of the command. Always 0 in single-player; lockstep multiplayer
|
|
// later merges commands from multiple sources into one ordered stream.
|
|
int playerId = 0;
|
|
};
|
|
|
|
// Places a building and, atomically, configures it. The configuration fields are
|
|
// bundled here (rather than as follow-up commands) because commands are applied
|
|
// at a deferred tick boundary, so the caller never sees the new BuildingId — the
|
|
// place-and-configure must happen as one unit inside apply().
|
|
struct PlaceBuildingCommand : Command
|
|
{
|
|
PlaceBuildingCommand() : Command(CommandKind::PlaceBuilding) {}
|
|
|
|
BuildingType type = BuildingType::Miner;
|
|
QPoint anchor;
|
|
Rotation rotation = Rotation::East;
|
|
|
|
// Optional configuration applied to the freshly placed (still-construction)
|
|
// building. The caller (UI) is responsible for unlock/validity pre-filtering;
|
|
// only fields that should apply are set.
|
|
std::optional<std::string> recipeId;
|
|
std::optional<ShipLayoutConfig> shipLayout;
|
|
bool hasSplitterFilters = false;
|
|
std::vector<ItemType> splitterFilterA;
|
|
std::vector<ItemType> splitterFilterB;
|
|
};
|
|
|
|
struct DemolishCommand : Command
|
|
{
|
|
DemolishCommand() : Command(CommandKind::Demolish) {}
|
|
BuildingId id = kInvalidBuildingId;
|
|
};
|
|
|
|
struct RotateInPlaceCommand : Command
|
|
{
|
|
RotateInPlaceCommand() : Command(CommandKind::RotateInPlace) {}
|
|
BuildingId id = kInvalidBuildingId;
|
|
Rotation newRotation = Rotation::East;
|
|
};
|
|
|
|
struct SetRecipeCommand : Command
|
|
{
|
|
SetRecipeCommand() : Command(CommandKind::SetRecipe) {}
|
|
BuildingId id = kInvalidBuildingId;
|
|
std::string recipeId;
|
|
};
|
|
|
|
struct SetShipLayoutCommand : Command
|
|
{
|
|
SetShipLayoutCommand() : Command(CommandKind::SetShipLayout) {}
|
|
BuildingId id = kInvalidBuildingId;
|
|
ShipLayoutConfig layout;
|
|
};
|
|
|
|
// Splitter filters for a queued / under-construction Splitter site (configured by
|
|
// BuildingSystem before the splitter is registered with BeltSystem).
|
|
struct SetSiteSplitterFiltersCommand : Command
|
|
{
|
|
SetSiteSplitterFiltersCommand() : Command(CommandKind::SetSiteSplitterFilters) {}
|
|
BuildingId id = kInvalidBuildingId;
|
|
std::vector<ItemType> filterA;
|
|
std::vector<ItemType> filterB;
|
|
};
|
|
|
|
// Splitter filters for an operational splitter, configured by tile via BeltSystem.
|
|
struct SetSplitterFiltersCommand : Command
|
|
{
|
|
SetSplitterFiltersCommand() : Command(CommandKind::SetSplitterFilters) {}
|
|
QPoint tile;
|
|
std::vector<ItemType> filterA;
|
|
std::vector<ItemType> filterB;
|
|
};
|
|
|
|
struct ClearBeltTilesCommand : Command
|
|
{
|
|
ClearBeltTilesCommand() : Command(CommandKind::ClearBeltTiles) {}
|
|
std::vector<QPoint> tiles;
|
|
};
|
|
|
|
struct ApplySchematicChoiceCommand : Command
|
|
{
|
|
ApplySchematicChoiceCommand() : Command(CommandKind::ApplySchematicChoice) {}
|
|
int choiceIndex = 0;
|
|
};
|
|
|
|
// Unlocks the next asteroid expansion (REQ-EXP-UNLOCK). Carries no payload: the
|
|
// simulation derives the cost and column count from its own expansion counter
|
|
// and config, so a recorded command replays identically.
|
|
struct ExpandAsteroidCommand : Command
|
|
{
|
|
ExpandAsteroidCommand() : Command(CommandKind::ExpandAsteroid) {}
|
|
};
|
|
|
|
// Restart boundary: reinitializes the simulation with a fresh seed and, if
|
|
// config is set, a reloaded config (GameConfig is move-only, so it is carried by
|
|
// shared_ptr and moved into the sim on apply). A null config keeps the current
|
|
// config. One replay file = one run between Reset boundaries.
|
|
struct ResetCommand : Command
|
|
{
|
|
ResetCommand() : Command(CommandKind::Reset) {}
|
|
std::shared_ptr<GameConfig> config; // null = keep current config
|
|
unsigned int seed = 0;
|
|
};
|