载入中...
搜索中...
未找到
Interaction.cpp
浏览该文件的文档.
1
7
8#include "common/Diagnostic.h"
9
10#include <algorithm>
11#include <utility>
12
13namespace eve::tactics {
14namespace {
15
16bool containsCell(const std::vector<Cell>& cells, Cell cell) {
17 return std::find(cells.begin(), cells.end(), cell) != cells.end();
18}
19
20} // namespace
21
22std::string_view interactionStateName(InteractionState state) noexcept {
23 switch (state) {
24 case InteractionState::Blocked: return "blocked";
25 case InteractionState::AwaitSelection: return "await_selection";
26 case InteractionState::UnitSelected: return "unit_selected";
27 case InteractionState::Targeting: return "targeting";
28 case InteractionState::Resolving: return "resolving";
29 case InteractionState::Ended: return "ended";
30 }
31 return "blocked";
32}
33
35 switch (kind) {
36 case InteractionIntentKind::None: return "none";
37 case InteractionIntentKind::SelectUnit: return "select_unit";
38 case InteractionIntentKind::MoveTo: return "move_to";
39 case InteractionIntentKind::UseAbilityOn: return "use_ability_on";
40 case InteractionIntentKind::Cancel: return "cancel";
41 case InteractionIntentKind::Confirm: return "confirm";
42 case InteractionIntentKind::EndTurn: return "end_turn";
43 }
44 return "none";
45}
46
47InteractionSession::InteractionSession(InteractionContext context) : context_(std::move(context)) {
48 // The initial state is computed once from the context; every later transition is an
49 // explicit handler, so there is exactly one place that decides "can the player act".
50 if (context_.status != BattleStatus::Running) {
52 } else if (context_.phase != BattlePhase::Acting) {
54 } else if (!context_.controlledUnit.isValid() || context_.activeUnit != context_.controlledUnit) {
56 } else {
58 }
59}
60
61InteractionSession InteractionSession::create(InteractionContext context) {
62 return InteractionSession(std::move(context));
63}
64
65InteractionState InteractionSession::state() const noexcept { return state_; }
66
67bool InteractionSession::acceptsInput() const noexcept {
68 return state_ != InteractionState::Blocked && state_ != InteractionState::Ended &&
69 state_ != InteractionState::Resolving;
70}
71
72bool InteractionSession::cellInReachable(Cell cell) const { return containsCell(context_.reachableCells, cell); }
73
74bool InteractionSession::cellInTargetable(Cell cell) const { return containsCell(armedTargets_, cell); }
75
76InteractionIntent InteractionSession::makeIntent(InteractionIntentKind kind, Cell cell) const {
77 InteractionIntent intent;
78 intent.kind = kind;
79 intent.actor = context_.controlledUnit;
80 intent.cell = cell;
81 intent.action = armedAction_;
82 const auto occupant = context_.unitsByCell.find(cell);
83 if (occupant != context_.unitsByCell.end()) intent.targetUnit = occupant->second;
84 intent.expected = context_.revision;
85 return intent;
86}
87
88Result<InteractionIntent> InteractionSession::onCellClicked(Cell cell) {
89 if (state_ == InteractionState::Ended)
91 "interaction session belongs to a finished battle",
92 "interaction.state"));
93 if (state_ == InteractionState::Blocked)
95 DiagnosticCode::PreconditionViolation, "interaction session is blocked", "interaction.state"));
96 if (state_ == InteractionState::Resolving)
99 "a pending interaction intent must be committed or dropped first", "interaction.state"));
100
101 if (state_ == InteractionState::Targeting) {
102 if (!cellInTargetable(cell)) return Result<InteractionIntent>::success(makeIntent(InteractionIntentKind::None, cell));
103 // The intent names both the cell and the occupying unit, so the caller can commit
104 // it without re-deriving which unit the click landed on.
105 pending_ = InteractionIntentKind::UseAbilityOn;
106 state_ = InteractionState::Resolving;
107 return Result<InteractionIntent>::success(makeIntent(pending_, cell));
108 }
109
110 // AwaitSelection / UnitSelected.
111 if (cell == context_.controlledCell) {
112 unitSelected_ = true;
113 state_ = InteractionState::UnitSelected;
114 pending_ = InteractionIntentKind::SelectUnit;
115 return Result<InteractionIntent>::success(makeIntent(pending_, cell));
116 }
117 if (cellInReachable(cell)) {
118 // A move is a complete intention on its own, so the machine reports it and lets the
119 // caller commit; the session is expected to be resolved or rebuilt afterwards.
120 pending_ = InteractionIntentKind::MoveTo;
121 state_ = InteractionState::Resolving;
122 return Result<InteractionIntent>::success(makeIntent(pending_, cell));
123 }
124 return Result<InteractionIntent>::success(makeIntent(InteractionIntentKind::None, cell));
125}
126
127Result<void> InteractionSession::armAbility(const LogicalId& action, std::vector<Cell> targetableCells) {
128 if (state_ == InteractionState::Ended || state_ == InteractionState::Blocked || state_ == InteractionState::Resolving)
130 DiagnosticCode::PreconditionViolation, "interaction session cannot arm an ability", "interaction.state"));
131 if (!action.isValid())
133 Diagnostic::error(DiagnosticCode::InvalidArgument, "armed ability requires a logical id", "action"));
134 armedAction_ = action;
135 armedTargets_ = std::move(targetableCells);
136 unitSelected_ = true;
137 state_ = InteractionState::Targeting;
138 pending_ = InteractionIntentKind::None;
140}
141
142Result<InteractionIntent> InteractionSession::onCancel() {
143 if (state_ == InteractionState::Ended)
145 "interaction session belongs to a finished battle",
146 "interaction.state"));
147 if (state_ == InteractionState::Blocked)
149 DiagnosticCode::PreconditionViolation, "interaction session is blocked", "interaction.state"));
150 if (state_ == InteractionState::Resolving)
153 "a pending interaction intent must be committed or dropped first", "interaction.state"));
154 if (state_ == InteractionState::AwaitSelection && !unitSelected_)
155 // Nothing to cancel is not a failure, but it must not be reported as if the player
156 // had cancelled something either.
157 return Result<InteractionIntent>::success(makeIntent(InteractionIntentKind::None, context_.controlledCell));
158
159 armedAction_ = LogicalId{};
160 armedTargets_.clear();
161 unitSelected_ = false;
162 pending_ = InteractionIntentKind::None;
163 state_ = InteractionState::AwaitSelection;
164 return Result<InteractionIntent>::success(makeIntent(InteractionIntentKind::Cancel, context_.controlledCell));
165}
166
167Result<InteractionIntent> InteractionSession::onEndTurn() {
168 if (state_ == InteractionState::Ended)
170 "interaction session belongs to a finished battle",
171 "interaction.state"));
172 if (state_ == InteractionState::Blocked || state_ == InteractionState::Resolving)
174 DiagnosticCode::PreconditionViolation, "interaction session cannot end the turn", "interaction.state"));
175 // Ending the turn is terminal for this activation, so the session moves to Resolving and
176 // the caller decides whether to commit it or rebuild from a fresh context.
177 pending_ = InteractionIntentKind::EndTurn;
178 armedAction_ = LogicalId{};
179 armedTargets_.clear();
180 unitSelected_ = false;
181 state_ = InteractionState::Resolving;
182 InteractionIntent intent = makeIntent(pending_, context_.controlledCell);
183 // `makeIntent` echoed the armed action, which this handler just cleared.
184 intent.action = LogicalId{};
185 return Result<InteractionIntent>::success(std::move(intent));
186}
187
188Result<void> InteractionSession::markResolving() {
189 if (state_ == InteractionState::Ended || state_ == InteractionState::Blocked)
191 "interaction session cannot resolve", "interaction.state"));
192 if (pending_ == InteractionIntentKind::None)
194 "interaction session has no pending intent to confirm",
195 "interaction.pending"));
196 state_ = InteractionState::Resolving;
198}
199
200Result<void> InteractionSession::resolve() {
201 if (state_ == InteractionState::Ended || state_ == InteractionState::Blocked)
203 "interaction session cannot resolve", "interaction.state"));
204 armedAction_ = LogicalId{};
205 armedTargets_.clear();
206 unitSelected_ = false;
207 pending_ = InteractionIntentKind::None;
208 state_ = InteractionState::AwaitSelection;
210}
211
212} // namespace eve::tactics
Stable, structured diagnostics shared by engine modules.
Board interaction state machine that returns intents instead of mutating.
TokenKind kind
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
std::string action
Definition PlayHost.cpp:117
Cell cell
const VegetationPresetContext & context
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
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
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
A disposable, non-authoritative interaction session.
std::string_view interactionStateName(InteractionState state) noexcept
Human-readable stable spelling of an interaction state (protocol text).
InteractionIntentKind
What the player asked for. A value, not a mutation.
Definition Interaction.h:45
@ UseAbilityOn
Declare InteractionIntent::action against cell and optional unit.
@ EndTurn
End the controlled unit's activation.
@ SelectUnit
Focus the controlled unit.
@ None
Nothing to do: the click landed somewhere with no meaning in this state.
@ Confirm
Submit whatever intent is pending.
@ MoveTo
Move the controlled unit onto InteractionIntent::cell.
@ Cancel
Armed ability/selection should be dropped.
std::string_view interactionIntentKindName(InteractionIntentKind kind) noexcept
Human-readable stable spelling of an intent kind (protocol text).
InteractionState
Where the interaction machine is, as far as the player is concerned.
Definition Interaction.h:29
@ Ended
The battle is over; only inspection remains.
@ Blocked
Not this controller's turn, or the battle is not accepting input at all.
@ Resolving
The caller has an uncommitted intent and must commit or cancel it.
@ Targeting
An ability is armed; the next click picks its target.
@ UnitSelected
The controlled unit is selected; the next click acts on a cell.
@ AwaitSelection
Nothing selected yet; a click picks a destination or the active unit.
Logical board coordinate independent of rendering projection.
Immutable projection of everything the interaction machine is allowed to know.
Definition Interaction.h:98
One player intention, expressed with stable identities only.
Definition Interaction.h:74
LogicalId action
Armed ability for InteractionIntentKind::UseAbilityOn.
Definition Interaction.h:80