Files
dota_factory/src/lib/sim/Command.h

162 lines
5.3 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,
Deconstruct,
CancelDeconstruction,
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;
};
// Marks a building for demolition: a construction site is removed instantly, a
// built building is queued for deconstruction (REQ-BLD-DECONSTRUCT, REQ-BLD-DECON-QUEUE).
struct DeconstructCommand : Command
{
DeconstructCommand() : Command(CommandKind::Deconstruct) {}
std::optional<BuildingId> id;
};
// Takes a building back out of the deconstruction queue (REQ-BLD-DECONSTRUCT-CLICK,
// REQ-BLD-DECONSTRUCT-BOX). No-op if the id is not currently queued.
struct CancelDeconstructionCommand : Command
{
CancelDeconstructionCommand() : Command(CommandKind::CancelDeconstruction) {}
std::optional<BuildingId> id;
};
struct RotateInPlaceCommand : Command
{
RotateInPlaceCommand() : Command(CommandKind::RotateInPlace) {}
std::optional<BuildingId> id;
Rotation newRotation = Rotation::East;
};
struct SetRecipeCommand : Command
{
SetRecipeCommand() : Command(CommandKind::SetRecipe) {}
std::optional<BuildingId> id;
std::string recipeId;
};
struct SetShipLayoutCommand : Command
{
SetShipLayoutCommand() : Command(CommandKind::SetShipLayout) {}
std::optional<BuildingId> id;
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) {}
std::optional<BuildingId> id;
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;
};