载入中...
搜索中...
未找到
TacticsBattle.cpp
浏览该文件的文档.
23bool containsHandle(const std::vector<ecs::EntityHandle>& values, const ecs::EntityHandle& handle) {
24 return std::any_of(values.begin(), values.end(), [&](const auto& value) { return sameHandle(value, handle); });
27void emit(Battle& battle, BattlePhase from, BattlePhase to, SimulationTick tick, std::string type,
48 const auto trigger = std::find_if(battle.events()->values.begin(), battle.events()->values.end(),
73 if (unit && unit->turn()->alive && sideSubject(unit->membership()->side) == objective.targetSide)
102 if (objective.status != ObjectiveStatus::Pending || !objectiveSatisfied(battle, objective.spec)) continue;
105 emit(battle, battle.turn()->phase, battle.turn()->phase, battle.turn()->tick, "objective.completed",
128Result<BattlePhase> transition(Battle& battle, BattlePhase next, SimulationTick tick, std::string type,
185 unit->turn()->charge = next < 0 ? 0 : (next > kMaxCharge ? kMaxCharge : static_cast<int>(next));
208void consumeActivationCharge(Battle& battle, ChargeModel model, const std::vector<ecs::EntityHandle>& activated) {
236 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics sides can only be added during setup", "battle.status"));
239 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics side handle is stale or has the wrong type", "side"));
242 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics side is already registered", "side"));
246 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics battle contains a stale side", "battle.sides"));
248 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics side subject is already registered", "side.subject"));
257Result<void> BattleSystem::addUnit(Battle& battle, ecs::EntityHandle unitHandle, ecs::EntityHandle sideHandle,
260 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics units can only be added during setup", "battle.status"));
263 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics unit handle is stale or has the wrong type", "unit"));
264 if (resolve<TacticalSide>(sideHandle) == nullptr || !containsHandle(battle.turn()->sides, sideHandle))
265 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics unit side is not registered with the battle", "side"));
267 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics unit is already registered", "unit"));
269 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics unit requires a valid SubjectRef", "unit.subject"));
273 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics battle contains a stale unit", "battle.units"));
275 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics unit subject is already registered", "unit.subject"));
283 battle.board()->value.remove(unit->identity()->subject).ignore("rollback tactics unit placement");
300 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics battle has already started", "battle.status"));
302 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle requires sides and units", "battle.setup"));
344Result<AbilityReceipt> validateAbility(Battle& battle, SubjectRef actor, const LogicalId& action, Cell targetCell,
347 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Acting || !turn->activeUnit)
349 DiagnosticCode::PreconditionViolation, "tactics battle is not accepting an ability", "battle.phase"));
352 Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics ability requires a valid action id", "action"));
356 Diagnostic::error(DiagnosticCode::StaleHandle, "tactics active unit is stale", "battle.activeUnit"));
364 DiagnosticCode::PreconditionViolation, "tactics unit has no action points left", "unit.actionPoints"));
377 DiagnosticCode::NotFound, "tactics ability target is not a unit of this battle", "targetUnit"));
380 DiagnosticCode::PreconditionViolation, "tactics ability target is already defeated", "targetUnit"));
384 DiagnosticCode::InvalidArgument, "tactics ability payload exceeds the persisted size bound", "payload"));
395Result<AbilityReceipt> BattleSystem::previewAbility(Battle& battle, SubjectRef actor, const LogicalId& action,
401Result<AbilityReceipt> BattleSystem::useAbility(Battle& battle, SubjectRef actor, const LogicalId& action,
413 Diagnostic::error(DiagnosticCode::StaleHandle, "tactics active unit is stale", "battle.activeUnit"));
429 return Result<AbilityReceipt>::success(std::move(validated).takeValue(), Status::success(StatusCode::Applied));
436 Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle is not running", "battle.status"));
439 DiagnosticCode::InvalidArgument, "tactics step requires a non-negative delta and increasing tick", "step"));
450 return transition(battle, BattlePhase::RoundStart, step.tick, "round.pending", committedRevision,
486 return Result<BattlePhase>::success(BattlePhase::RoundStart, Status::success(StatusCode::NoOp));
514 } while (turn->cursor < turn->schedule.size() && !scheduleEntryAlive(turn->schedule[turn->cursor]));
538 return transition(battle, BattlePhase::RoundStart, step.tick, "round.completed", committedRevision,
557 Diagnostic::error(DiagnosticCode::InvariantViolation, "tactics battle phase is invalid", "battle.phase"));
563 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Acting || !turn->activeUnit)
564 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle is not accepting end-turn", "battle.phase"));
567 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics active unit is stale", "battle.activeUnit"));
569 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics actor does not own the active turn", "actor"));
593 DiagnosticCode::InvalidArgument, "tactics movement command tick cannot move backward", "commandTick"));
614 emit(battle, BattlePhase::Acting, BattlePhase::BattleEnd, turn->tick, "battle.objective_completed",
631Result<void> BattleSystem::faceUnit(Battle& battle, SubjectRef actor, int facing, SimulationTick commandTick) {
633 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics facing command tick cannot move backward", "commandTick"));
639 if (unit->position()->facing == facing) return Result<void>::success(Status::success(StatusCode::NoOp));
645 emit(battle, BattlePhase::Acting, BattlePhase::Acting, turn->tick, "unit.facing_changed", actor);
658 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Acting || !turn->activeUnit)
659 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle is not accepting facing changes", "battle.phase"));
662 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics active unit is stale", "battle.activeUnit"));
668 Diagnostic::error(DiagnosticCode::Unsupported, "graph boards have no coordinate facing", "facing"));
673 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics facing is invalid for board topology", "facing"));
682Result<void> BattleSystem::waitUnit(Battle& battle, SubjectRef actor, SimulationTick commandTick) {
684 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics wait command tick cannot move backward", "commandTick"));
708 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Acting || !turn->activeUnit)
709 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle is not accepting wait", "battle.phase"));
712 return Result<void>::failure(Diagnostic::error(DiagnosticCode::StaleHandle, "tactics active unit is stale", "battle.activeUnit"));
714 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics actor does not own the active turn", "actor"));
718Result<MoveReceipt> BattleSystem::previewMove(Battle& battle, SubjectRef actor, Cell destination) {
720 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Acting || !turn->activeUnit)
722 DiagnosticCode::PreconditionViolation, "tactics battle is not accepting movement", "battle.phase"));
726 Diagnostic::error(DiagnosticCode::StaleHandle, "tactics active unit is stale", "battle.activeUnit"));
731 auto path = PathQuery::path(battle.board()->value, actor, destination, unit->turn()->movePoints);
749 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "only a running tactics battle can finish", "battle.status"));
756 emit(battle, from, BattlePhase::BattleEnd, turn->tick, "battle.ended", battle.identity()->subject);
776 DiagnosticCode::InvalidArgument, "tactics reaction window requires a trigger and candidates", "reaction"));
777 const auto triggerEvent = std::find_if(battle.events()->values.begin(), battle.events()->values.end(),
781 DiagnosticCode::NotFound, "tactics reaction trigger event does not exist", "reaction.triggerSequence"));
785 DiagnosticCode::PreconditionViolation, "tactics reaction stack depth exceeded", "reaction.depth"));
796 if (left.reactor.format() != right.reactor.format()) return left.reactor.format() < right.reactor.format();
820Result<ReactionReceipt> BattleSystem::acceptReaction(Battle& battle, SubjectRef reactor, const LogicalId& action) {
824 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Reaction || reactions->stack.empty())
826 DiagnosticCode::PreconditionViolation, "tactics battle has no open reaction window", "reaction"));
828 const auto candidate = std::find_if(window.candidates.begin(), window.candidates.end(), [&](const auto& value) {
833 DiagnosticCode::NotFound, "tactics reaction is not eligible in the top window", "reaction.candidate"));
834 const std::string key = std::to_string(window.triggerSequence) + ":" + reactor.format() + ":" + action.format();
837 DiagnosticCode::Conflict, "tactics reaction already fired for this trigger chain", "reaction.cycle"));
849 DiagnosticCode::NotFound, "tactics reacting unit is not part of the battle", "reaction.reactor"));
872 {trigger, reactor, action, reactingUnit->turn()->reactionPoints}, Status::success(StatusCode::Applied));
879 if (turn->status != BattleStatus::Running || turn->phase != BattlePhase::Reaction || reactions->stack.empty())
880 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle has no open reaction window", "reaction"));
899 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics objectives can only be added during setup", "battle.status"));
901 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "tactics objective requires valid identities", "objective"));
908 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics objective beneficiary side is not registered", "objective.beneficiarySide"));
911 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics elimination target side is not registered", "objective.targetSide"));
913 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "survive-rounds objective requires a positive round", "objective.requiredRound"));
916 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "occupy-cells objective requires cells", "objective.requiredCells"));
918 objective.requiredCells.erase(std::unique(objective.requiredCells.begin(), objective.requiredCells.end()),
922 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "objective cell does not exist on the board", "objective.requiredCells"));
924 const auto duplicate = std::find_if(battle.objectives()->values.begin(), battle.objectives()->values.end(),
927 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "tactics objective ID already exists", "objective.id"));
939 return Result<void>::failure(Diagnostic::error(DiagnosticCode::PreconditionViolation, "tactics battle is not accepting outcomes", "battle.status"));
941 if (!target) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "tactics defeat target is not in the battle", "unit"));
972 DiagnosticCode::PreconditionViolation, "tactics random streams require a running battle", "battle.status"));
979 stream.format(), Battle::RandomStreamState{initialStreamState(battle.identity()->seed, stream.format()), 0});
1001 std::find_if(sides.begin(), sides.end(), [&](const auto& value) { return sameHandle(value, side); });
1018 Diagnostic::error(DiagnosticCode::StaleHandle, "tactics battle contains a stale unit", "battle.units"));
Checked battle setup and deterministic phase systems.
Pluggable deterministic turn-scheduling policies.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
bool isValid() const noexcept
Returns whether this value contains a valid namespace and name.
Definition Identity.h:433
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
static constexpr StrongUint64 zero() noexcept
Returns the zero value for this strong type.
Definition StrongUint64.h:24
static Result< AbilityReceipt > previewAbility(Battle &battle, SubjectRef actor, const LogicalId &action, Cell targetCell, SubjectRef targetUnit={}, std::string_view payload={})
Preview an ability declaration with the exact validator used by commit.
Definition TacticsBattle.cpp:395
static Result< void > endTurn(Battle &battle, SubjectRef actor)
End the currently acting unit's turn and enter TurnEnd.
Definition TacticsBattle.cpp:560
static Result< void > defeatUnit(Battle &battle, SubjectRef unit)
Apply a tactical defeat outcome and evaluate affected objectives atomically.
Definition TacticsBattle.cpp:935
static Result< std::size_t > openReaction(Battle &battle, std::uint64_t triggerSequence, std::vector< ReactionCandidate > candidates)
Open a sorted reaction window for an existing battle event sequence.
Definition TacticsBattle.cpp:764
static Result< AbilityReceipt > useAbility(Battle &battle, SubjectRef actor, const LogicalId &action, Cell targetCell, SubjectRef targetUnit={}, std::string payload={})
Declare an ability activation by the active unit.
Definition TacticsBattle.cpp:401
static Result< void > addObjective(Battle &battle, ObjectiveSpec objective)
Register a validated built-in objective during setup.
Definition TacticsBattle.cpp:896
static Result< std::uint64_t > roll(Battle &battle, const LogicalId &stream)
Consume one value from a battle-owned named deterministic stream.
Definition TacticsBattle.cpp:967
static Result< void > declineReaction(Battle &battle)
Decline and close the top reaction window without consuming resources.
Definition TacticsBattle.cpp:875
static Result< void > finish(Battle &battle)
End a running battle and enter BattleEnd.
Definition TacticsBattle.cpp:745
static Result< MoveReceipt > previewMove(Battle &battle, SubjectRef actor, Cell destination)
Preview movement with the exact validator used by commit, without mutation.
Definition TacticsBattle.cpp:718
static Result< void > addSide(Battle &battle, ecs::EntityHandle side)
Register a side during setup using a generation-checked handle.
Definition TacticsBattle.cpp:234
static Result< void > faceUnit(Battle &battle, SubjectRef actor, int facing)
Change the active unit's logical facing without consuming movement points.
Definition TacticsBattle.cpp:627
static Result< ReactionReceipt > acceptReaction(Battle &battle, SubjectRef reactor, const LogicalId &action)
Accept one eligible reaction, consume one reaction point and close the top window.
Definition TacticsBattle.cpp:820
static Result< void > previewFace(Battle &battle, SubjectRef actor, int facing)
Preview a facing action with the exact validator used by commit.
Definition TacticsBattle.cpp:656
static Result< BattlePhase > advance(Battle &battle, const SimulationStep &step)
Advance exactly one observable automatic phase transition.
Definition TacticsBattle.cpp:432
static Result< void > waitUnit(Battle &battle, SubjectRef actor)
Commit a wait action and enter TurnEnd for the active unit.
Definition TacticsBattle.cpp:678
static Result< MoveReceipt > moveUnit(Battle &battle, SubjectRef actor, Cell destination)
Validate and atomically commit movement for the active unit.
Definition TacticsBattle.cpp:585
static Result< void > addUnit(Battle &battle, ecs::EntityHandle unit, ecs::EntityHandle side, Cell cell)
Register and place a unit during setup atomically.
Definition TacticsBattle.cpp:257
static Result< void > previewWait(Battle &battle, SubjectRef actor)
Preview a wait action with the exact validator used by commit.
Definition TacticsBattle.cpp:706
static Result< void > start(Battle &battle, std::string policyId)
Start a configured battle and emit battle.started.
Definition TacticsBattle.cpp:296
Battle short-root and authoritative owner of board and turn state.
Definition TacticsTypes.h:444
static Result< std::vector< Cell > > path(const BoardState &board, SubjectRef subject, Cell target, int budget)
Compute a minimum-cost origin-to-target path within budget.
Definition TacticsPath.cpp:154
static Result< Reachability > reachable(const BoardState &board, SubjectRef subject, int budget)
Compute all unoccupied reachable cells for a placed subject.
Definition TacticsPath.cpp:138
static TurnPolicyRegistry & builtins()
Return the process-wide registry carrying the built-in policies.
Definition TurnPolicy.cpp:111
Result< const ITurnPolicy * > find(std::string_view id) const
Resolve a registered policy by id, or NotFound.
Definition TurnPolicy.cpp:94
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
Definition Interaction.cpp:13
@ Completed
Result< AbilityReceipt > validateAbility(Battle &battle, SubjectRef actor, const LogicalId &action, Cell targetCell, SubjectRef targetUnit, std::string_view payload)
Validate one ability declaration without mutating anything.
Definition TacticsBattle.cpp:344
@ Running
@ RollRandom
@ AcceptReaction
@ DeclineReaction
@ OpenReaction
@ UseAbility
Declare a unit ability activation against a target cell.
@ DefeatUnit
@ OccupyCells
@ SurviveRounds
@ EliminateSide
@ BattleStart
@ RoundEnd
@ Reaction
@ TurnStart
@ TurnEnd
@ RoundStart
@ BattleEnd
constexpr std::size_t kMaxAbilityPayloadBytes
Upper bound on the opaque ability payload a declaration may carry.
Definition TacticsTypes.h:353
@ Square4
@ Square8
@ ExplicitGraph
@ InvalidArgument
@ NotFound
@ PreconditionViolation
@ Unsupported
@ InvariantViolation
@ StaleHandle
@ Conflict
@ NoOp
@ Applied
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
detail::StrongUint64< detail::RevisionTag > Revision
Monotonic content/state revision used for optimistic-concurrency checks.
Definition Revision.h:13
One declared ability activation and the resource state it left behind.
Definition TacticsBattle.h:29
int remainingActionPoints
Remaining action points after the activation was paid for.
Definition TacticsBattle.h:36
SubjectRef targetUnit
The targeted unit, or an invalid subject when the ability targets a cell only.
Definition TacticsBattle.h:33
One accepted replay command using only persistent identities and values.
Definition TacticsTypes.h:386
SubjectRef targetUnit
The unit this command targeted, when the command targets one.
Definition TacticsTypes.h:401
std::string payload
Opaque caller-owned effect parameters, as declared by the caller.
Definition TacticsTypes.h:411
std::vector< ReactionCandidate > candidates
Definition TacticsTypes.h:416
std::uint64_t triggerSequence
Definition TacticsTypes.h:414
One named deterministic random stream's serializable state.
Definition TacticsTypes.h:513
Logical board coordinate independent of rendering projection.
Definition TacticsTypes.h:30
The charge rule a policy declares for round scheduling.
Definition TurnPolicy.h:63
Committed tactical movement and remaining resource state.
Definition TacticsBattle.h:16
Persistent objective definition using stable side identities.
Definition TacticsTypes.h:426
std::vector< Cell > requiredCells
Definition TacticsTypes.h:432
One open reaction window on the battle-owned LIFO stack.
Definition TacticsTypes.h:332