载入中...
搜索中...
未找到
TacticsAction.cpp
浏览该文件的文档.
2
3#include "common/Assert.h"
4
5#include <cstdint>
6#include <charconv>
7#include <limits>
8#include <utility>
9
10namespace eve::tactics {
11namespace {
12
13Result<int> integerParameter(const Value::Object& parameters, std::string_view key) {
14 const auto found = parameters.find(std::string(key));
15 if (found == parameters.end() || !found->second.isInt64())
17 "tactics move parameter must be an integer",
18 "request.parameters." + std::string(key)));
19 const std::int64_t value = found->second.asInt();
20 if (value < std::numeric_limits<int>::min() || value > std::numeric_limits<int>::max())
22 "tactics move parameter is outside int range",
23 "request.parameters." + std::string(key)));
24 return Result<int>::success(static_cast<int>(value));
25}
26
27Result<Revision> revisionParameter(const Value::Object& parameters) {
28 const auto found = parameters.find("expectedRevision");
29 const auto* text = found == parameters.end() ? nullptr : found->second.getIf<std::string>();
30 if (!text)
32 "tactics move requires an expected revision decimal string",
33 "request.parameters.expectedRevision"));
34 std::uint64_t value = 0;
35 const auto [end, error] = std::from_chars(text->data(), text->data() + text->size(), value);
36 if (error != std::errc{} || end != text->data() + text->size())
38 "tactics expected revision is invalid",
39 "request.parameters.expectedRevision"));
41}
42
43class PreparedMoveOperation final : public action::IActionEffectOperation {
44public:
45 PreparedMoveOperation(Battle& battle, SubjectRef actor, Cell destination, SimulationTick tick)
46 : battle_(battle), actor_(actor), destination_(destination), tick_(tick) {}
47
48 void commit() noexcept override {
49 auto committed = BattleSystem::moveUnit(battle_, actor_, destination_, tick_);
50 const bool ok = committed.ok();
51 EV_ASSERT(ok, "prepared tactics move changed between synchronous prepare and commit");
52 if (ok) std::move(committed).takeValue();
53 }
54
55 void rollback() noexcept override {}
56
57private:
58 Battle& battle_;
59 SubjectRef actor_;
60 Cell destination_;
61 SimulationTick tick_;
62};
63
64class PreparedFaceOperation final : public action::IActionEffectOperation {
65public:
66 PreparedFaceOperation(Battle& battle, SubjectRef actor, int facing, SimulationTick tick)
67 : battle_(battle), actor_(actor), facing_(facing), tick_(tick) {}
68
69 void commit() noexcept override {
70 auto committed = BattleSystem::faceUnit(battle_, actor_, facing_, tick_);
71 const bool ok = committed.ok();
72 EV_ASSERT(ok, "prepared tactics face changed between synchronous prepare and commit");
73 }
74
75 void rollback() noexcept override {}
76
77private:
78 Battle& battle_;
79 SubjectRef actor_;
80 int facing_ = 0;
81 SimulationTick tick_;
82};
83
84class PreparedWaitOperation final : public action::IActionEffectOperation {
85public:
86 PreparedWaitOperation(Battle& battle, SubjectRef actor, SimulationTick tick)
87 : battle_(battle), actor_(actor), tick_(tick) {}
88
89 void commit() noexcept override {
90 auto committed = BattleSystem::waitUnit(battle_, actor_, tick_);
91 const bool ok = committed.ok();
92 EV_ASSERT(ok, "prepared tactics wait changed between synchronous prepare and commit");
93 }
94
95 void rollback() noexcept override {}
96
97private:
98 Battle& battle_;
99 SubjectRef actor_;
100 SimulationTick tick_;
101};
102
103Result<std::pair<TacticalUnit*, Battle*>> requestOwners(ecs::EntityHandle unit) {
104 auto* tacticalUnit = dynamic_cast<TacticalUnit*>(ecs::try_get(unit));
105 if (!tacticalUnit)
106 return Result<std::pair<TacticalUnit*, Battle*>>::failure(Diagnostic::error(
107 DiagnosticCode::StaleHandle, "tactics action source is stale or has the wrong type", "unit"));
108 auto* battle = dynamic_cast<Battle*>(ecs::try_get(tacticalUnit->membership()->battle));
109 if (!battle)
110 return Result<std::pair<TacticalUnit*, Battle*>>::failure(
111 Diagnostic::error(DiagnosticCode::StaleHandle, "tactics action source battle is stale", "unit.battle"));
112 return Result<std::pair<TacticalUnit*, Battle*>>::success({tacticalUnit, battle});
113}
114
115action::ActionRequest baseRequest(LogicalId id, ecs::EntityHandle unit, Battle& battle, SimulationTick tick) {
116 action::ActionRequest request;
117 request.actionId = std::move(id);
118 request.source = unit;
119 request.requestedTick = tick;
120 request.parameters.emplace("expectedRevision", Value(std::to_string(battle.turn()->revision.value())));
121 return request;
122}
123
124} // namespace
125
127 const action::ActionDefinition& definition, const action::ActionRequest& request, const sensing::TargetSet* targets,
129 (void)targets;
130 const auto moveId = LogicalId::parse("tactics:move");
131 const auto faceId = LogicalId::parse("tactics:face");
132 const auto waitId = LogicalId::parse("tactics:wait");
133 if (!moveId || !faceId || !waitId || definition.id != request.actionId ||
134 (definition.id != *moveId && definition.id != *faceId && definition.id != *waitId))
137 "tactics executor received an unsupported action definition", "definition.id"));
138 if (!request.source)
140 DiagnosticCode::InvalidArgument, "tactics action request requires a source unit", "request.source"));
141 auto* unit = dynamic_cast<TacticalUnit*>(ecs::try_get(*request.source));
142 if (unit == nullptr)
144 DiagnosticCode::StaleHandle, "tactics action source is stale or has the wrong type", "request.source"));
145 auto expectedRevision = revisionParameter(request.parameters);
146 if (!expectedRevision)
147 return Result<std::unique_ptr<action::IActionEffectOperation>>::failure(expectedRevision.status());
148 if (battle_.turn()->revision != expectedRevision.value())
150 DiagnosticCode::Conflict, "tactics action request was prepared against an older battle revision",
151 "request.parameters.expectedRevision"));
152
153 if (definition.id == *moveId) {
154 auto x = integerParameter(request.parameters, "x");
155 auto y = integerParameter(request.parameters, "y");
156 auto layer = integerParameter(request.parameters, "layer");
157 if (!x) return Result<std::unique_ptr<action::IActionEffectOperation>>::failure(x.status());
158 if (!y) return Result<std::unique_ptr<action::IActionEffectOperation>>::failure(y.status());
160 const Cell destination{x.value(), y.value(), layer.value()};
161 auto preview = BattleSystem::previewMove(battle_, unit->identity()->subject, destination);
162 if (!preview) return Result<std::unique_ptr<action::IActionEffectOperation>>::failure(preview.status());
163 std::move(preview).takeValue();
165 std::make_unique<PreparedMoveOperation>(battle_, unit->identity()->subject, destination, tick),
167 }
168 if (definition.id == *faceId) {
169 auto facing = integerParameter(request.parameters, "facing");
171 auto preview = BattleSystem::previewFace(battle_, unit->identity()->subject, facing.value());
172 if (!preview) return Result<std::unique_ptr<action::IActionEffectOperation>>::failure(preview.status());
174 std::make_unique<PreparedFaceOperation>(battle_, unit->identity()->subject, facing.value(), tick),
176 }
177 auto preview = BattleSystem::previewWait(battle_, unit->identity()->subject);
178 if (!preview) return Result<std::unique_ptr<action::IActionEffectOperation>>::failure(preview.status());
180 std::make_unique<PreparedWaitOperation>(battle_, unit->identity()->subject, tick),
182}
183
185 action::ActionDefinition definition;
186 definition.id = *LogicalId::parse("tactics:move");
187 definition.activeExecutionRequired = true;
188 return definition;
189}
190
192 action::ActionDefinition definition;
193 definition.id = *LogicalId::parse("tactics:face");
194 definition.activeExecutionRequired = true;
195 return definition;
196}
197
199 action::ActionDefinition definition;
200 definition.id = *LogicalId::parse("tactics:wait");
201 definition.activeExecutionRequired = true;
202 return definition;
203}
204
206 auto owners = requestOwners(unit);
207 if (!owners) return Result<action::ActionRequest>::failure(owners.status());
208 action::ActionRequest request = baseRequest(*LogicalId::parse("tactics:move"), unit, *owners.value().second, tick);
209 request.parameters.emplace("x", Value(destination.x));
210 request.parameters.emplace("y", Value(destination.y));
211 request.parameters.emplace("layer", Value(destination.layer));
213}
214
216 auto owners = requestOwners(unit);
217 if (!owners) return Result<action::ActionRequest>::failure(owners.status());
218 action::ActionRequest request = baseRequest(*LogicalId::parse("tactics:face"), unit, *owners.value().second, tick);
219 request.parameters.emplace("facing", Value(facing));
221}
222
224 auto owners = requestOwners(unit);
225 if (!owners) return Result<action::ActionRequest>::failure(owners.status());
227 baseRequest(*LogicalId::parse("tactics:wait"), unit, *owners.value().second, tick));
228}
229
230} // namespace eve::tactics
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
EVEngine assertion entry point, backed by zeroerr.
#define EV_ASSERT(cond,...)
Assert an internal engine invariant (state that must always hold).
Definition Assert.h:37
const GltfImportRequest & request
std::uint32_t key
std::string text
ModuleFacing facing
std::string error
Definition Package.cpp:60
TileLayer * layer
bool found
Tactics movement adapter for shared ActionRuntime.
TacticalUnit * unit
SimulationTick tick
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
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
Owning target result containing primary-independent subjects and optional geometry.
Definition Targeting.h:384
static Result< MoveReceipt > previewMove(Battle &battle, SubjectRef actor, Cell destination)
Preview movement with the exact validator used by commit, without mutation.
static Result< void > faceUnit(Battle &battle, SubjectRef actor, int facing)
Change the active unit's logical facing without consuming movement points.
static Result< void > previewFace(Battle &battle, SubjectRef actor, int facing)
Preview a facing action with the exact validator used by commit.
static Result< void > waitUnit(Battle &battle, SubjectRef actor)
Commit a wait action and enter TurnEnd for the active unit.
static Result< MoveReceipt > moveUnit(Battle &battle, SubjectRef actor, Cell destination)
Validate and atomically commit movement for the active unit.
static Result< void > previewWait(Battle &battle, SubjectRef actor)
Preview a wait action with the exact validator used by commit.
Tactical unit short-root entity.
Result< std::unique_ptr< action::IActionEffectOperation > > prepare(const action::ActionDefinition &definition, const action::ActionRequest &request, const sensing::TargetSet *targets, SimulationTick tick) override
Prepare the active operation once without applying its effect.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
@ Cell
A cell was removed; the out-parameter holds it.
::eve::SubjectRef SubjectRef
Strong reference to a runtime subject.
Definition Targeting.h:76
Result< action::ActionRequest > makeMoveRequest(ecs::EntityHandle unit, Cell destination, SimulationTick tick)
Build a checked move request using canonical integer cell parameters.
action::ActionDefinition faceActionDefinition()
Build the canonical zero-duration tactics facing action definition.
Result< action::ActionRequest > makeWaitRequest(ecs::EntityHandle unit, SimulationTick tick)
Build a checked wait request with optimistic battle revision.
action::ActionDefinition moveActionDefinition()
Build the canonical zero-duration tactics move action definition.
action::ActionDefinition waitActionDefinition()
Build the canonical zero-duration tactics wait action definition.
Result< action::ActionRequest > makeFaceRequest(ecs::EntityHandle unit, int facing, SimulationTick tick)
Build a checked facing request with optimistic battle revision.
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
Immutable-by-convention definition of one action kind.
Definition Action.h:90
One caller-owned request to instantiate an action execution.
Definition Action.h:127
Logical board coordinate independent of rendering projection.