载入中...
搜索中...
未找到
BattleControl.cpp
浏览该文件的文档.
1#include "rpg/BattleControl.h"
2
3#include "common/Capability.h"
4#include "rpg/Battle.h"
5#include "rpg/RPGActor.h"
6
7#include <algorithm>
8
9namespace eve::rpg {
10namespace {
11
12LogicalId gameplayId(std::string_view value) { return LogicalId::parse(value).value(); }
13
14Result<std::string> stringParameter(const Value& parameters, std::string_view name,
15 bool optional = false) {
16 const auto* object = parameters.getIf<Value::Object>();
17 if (!object)
19 DiagnosticCode::InvalidArgument, "RPG gameplay parameters must be an object", "parameters"));
20 const auto found = object->find(std::string(name));
21 if (found == object->end() && optional) return Result<std::string>::success({});
22 if (found == object->end() || !found->second.isString())
24 "RPG gameplay parameter must be a string",
25 "parameters." + std::string(name)));
26 return Result<std::string>::success(found->second.asString());
27}
28
29} // namespace
30
32 : battle_(battle), instance_(instance) {
33 cap::addListener<IGameplayControlProvider>(this);
34 cap::addListener<IGameplayInstanceCatalog>(this);
35}
36
38 cap::removeListener<IGameplayInstanceCatalog>(this);
39 cap::removeListener<IGameplayControlProvider>(this);
40}
41
43 if (!instance_.isValid() || !subject.isValid() || !actor)
45 DiagnosticCode::InvalidArgument, "battle, subject, and actor identities must be valid", "participant"));
46 bool participant = false;
47 for (int index = 0; index < battle_.getActorCount(); ++index)
48 if (battle_.getActor(index) == actor) participant = true;
49 if (!participant)
51 Diagnostic::error(DiagnosticCode::NotFound, "actor is not a participant in the adapted battle", "actor"));
52 const auto existing = actors_.find(subject);
53 if (existing != actors_.end() && existing->second != actor)
55 Diagnostic::error(DiagnosticCode::Conflict, "subject is already bound to another participant", "subject"));
56 if (const auto reverse = subjectOf(actor); reverse.isValid() && reverse != subject)
58 Diagnostic::error(DiagnosticCode::Conflict, "actor is already bound to another subject", "actor"));
59 actors_[subject] = actor;
60 ++revision_;
62}
63
64std::string_view BattleControl::gameplayDomain() const noexcept { return "rpg.battle"; }
65
66std::vector<SubjectRef> BattleControl::gameplayInstances() const {
67 // This adapter serves exactly one instance: the identity it was constructed
68 // with is the only one it can answer for.
69 if (!instance_.isValid()) return {};
70 return {instance_};
71}
72
73bool BattleControl::controls(const GameplaySession& session, SubjectRef subject) const {
75 std::find(session.controlledSubjects.begin(), session.controlledSubjects.end(), subject) !=
76 session.controlledSubjects.end();
77}
78
79RPGActor* BattleControl::resolve(SubjectRef subject) const {
80 const auto found = actors_.find(subject);
81 return found == actors_.end() ? nullptr : found->second;
82}
83
84SubjectRef BattleControl::subjectOf(RPGActor* actor) const {
85 for (const auto& [subject, candidate] : actors_)
86 if (candidate == actor) return subject;
87 return SubjectRef::nil();
88}
89
91 SubjectRef instance) const {
92 if (instance != instance_ || !instance_.isValid())
94 Diagnostic::error(DiagnosticCode::NotFound, "RPG battle gameplay instance was not found", "instance"));
95 bool ownsParticipant = false;
96 Value::Array participants;
97 for (int index = 0; index < battle_.getActorCount(); ++index) {
98 RPGActor* actor = battle_.getActor(index);
99 const auto subject = subjectOf(actor);
100 if (!subject.isValid()) continue;
101 const bool controlled = controls(session, subject);
102 ownsParticipant = ownsParticipant || controlled;
103 participants.emplace_back(Value::Object{
104 {"alive", Value(battle_.isActorAlive(actor))},
105 {"controlled", Value(controlled)},
106 {"hp", Value(actor->getCurrent("hp"))},
107 {"maxHp", Value(actor->getMax("hp"))},
108 {"side", Value(battle_.getSide(index))},
109 {"subject", Value(subject.format())},
110 });
111 }
112 if (session.access == GameplayAccess::PlayerEquivalent && !ownsParticipant)
115 "session controls no participant in this RPG battle", "session.controlledSubjects"));
116 GameplayObservation observation;
117 observation.domain = gameplayId("gameplay:rpg-battle");
118 observation.instance = instance_;
119 observation.tick = tick_;
120 observation.revision = revision_;
121 observation.state = Value(Value::Object{
122 {"defeat", Value(battle_.isDefeat())},
123 {"finished", Value(battle_.isFinished())},
124 {"participants", Value(std::move(participants))},
125 {"turn", Value(battle_.getTurn())},
126 {"victory", Value(battle_.isVictory())},
127 });
128 return Result<GameplayObservation>::success(std::move(observation));
129}
130
132 const GameplaySession& session, SubjectRef instance, SubjectRef subject) const {
133 if (instance != instance_)
135 Diagnostic::error(DiagnosticCode::NotFound, "RPG battle gameplay instance was not found", "instance"));
136 RPGActor* actor = resolve(subject);
137 if (!actor)
139 Diagnostic::error(DiagnosticCode::NotFound, "RPG battle participant was not found", "subject"));
140 if (!controls(session, subject))
142 DiagnosticCode::PreconditionViolation, "session does not control this RPG participant", "subject"));
143 if (!battle_.isActorAlive(actor) || battle_.isFinished())
145 const Value targetSchema(Value::Object{{"type", Value("subject")}});
146 const Value skillSchema(Value::Object{{"skillId", Value(Value::Object{{"type", Value("string")}})},
147 {"target", targetSchema}});
149 {gameplayId("rpg:attack"), Value(Value::Object{{"target", targetSchema}})},
150 {gameplayId("rpg:skill"), skillSchema},
151 });
152}
153
155 SubjectRef instance,
156 const GameplayCommand& command) {
157 if (instance != instance_)
159 Diagnostic::error(DiagnosticCode::NotFound, "RPG battle gameplay instance was not found", "instance"));
160 RPGActor* actor = resolve(command.subject);
161 if (!actor)
163 Diagnostic::error(DiagnosticCode::NotFound, "RPG battle participant was not found", "command.subject"));
164 if (!controls(session, command.subject))
166 DiagnosticCode::PreconditionViolation, "session does not control this RPG participant", "command.subject"));
167 if (command.id.empty())
169 Diagnostic::error(DiagnosticCode::InvalidArgument, "command id must not be empty", "command.id"));
170 if (command.observedTick != tick_ || command.expectedRevision != revision_)
172 DiagnosticCode::Conflict, "RPG command was based on a stale observation", "command.expectedRevision"));
173 auto targetText = stringParameter(command.parameters, "target");
174 if (!targetText) return Result<GameplayCommandReceipt>::failure(targetText.status());
175 const auto targetId = PersistentId::parse(targetText.value());
177 if (!target)
179 Diagnostic::error(DiagnosticCode::NotFound, "RPG command target was not found", "parameters.target"));
180 std::string skillId;
181 if (command.action == gameplayId("rpg:skill")) {
182 auto skill = stringParameter(command.parameters, "skillId");
183 if (!skill) return Result<GameplayCommandReceipt>::failure(skill.status());
184 skillId = std::move(skill).takeValue();
185 } else if (command.action != gameplayId("rpg:attack")) {
187 Diagnostic::error(DiagnosticCode::Unsupported, "unsupported RPG gameplay action", "command.action"));
188 }
189 auto queued = battle_.setActionChecked(actor, skillId, target);
191 ++revision_;
192 lastCommandByActor_[actor] = command.id;
193 GameplayEvent accepted;
194 accepted.sequence = nextEventSequence_++;
195 accepted.tick = tick_;
196 accepted.type = "rpg.command.accepted";
197 accepted.subject = command.subject;
198 accepted.causationCommandId = command.id;
199 accepted.correlationId = command.id;
200 accepted.payload = Value(Value::Object{{"action", Value(command.action.format())}});
201 events_.push_back(std::move(accepted));
202
204 receipt.commandId = command.id;
205 receipt.executionId = "rpg-round-" + std::to_string(battle_.getTurn() + 1);
206 receipt.acceptedTick = tick_;
207 receipt.resultingRevision = revision_;
208 receipt.details = Value(Value::Object{{"skillId", Value(skillId)},
209 {"target", Value(targetText.value())}});
210 return Result<GameplayCommandReceipt>::success(std::move(receipt),
212}
213
215 SubjectRef instance,
216 const SimulationStep& step) {
217 if (instance != instance_)
219 Diagnostic::error(DiagnosticCode::NotFound, "RPG battle gameplay instance was not found", "instance"));
220 if (step.tick <= tick_)
222 Diagnostic::error(DiagnosticCode::Conflict, "RPG simulation tick must increase", "step.tick"));
223 auto authorized = observeGameplay(session, instance);
224 if (!authorized) return Result<GameplayObservation>::failure(authorized.status());
225 std::move(authorized).takeValue();
226 battle_.autoEnemyActions();
227 battle_.startRound();
228 while (battle_.executeNextAction() && !battle_.isFinished()) {}
229 battle_.pollEvents();
230 tick_ = step.tick;
231 ++revision_;
232 for (int index = 0; index < battle_.getEventCount(); ++index) {
233 RPGActor* caster = battle_.getEventCaster(index);
234 GameplayEvent event;
235 event.sequence = nextEventSequence_++;
236 event.tick = tick_;
237 event.type = "rpg.battle." + battle_.getEventAction(index);
238 event.subject = subjectOf(caster ? caster : battle_.getEventTarget(index));
239 if (const auto found = lastCommandByActor_.find(caster); found != lastCommandByActor_.end()) {
240 event.causationCommandId = found->second;
241 event.correlationId = found->second;
242 }
243 event.payload = Value(Value::Object{
244 {"amount", Value(battle_.getEventAmount(index))},
245 {"critical", Value(battle_.getEventCrit(index))},
246 {"skillId", Value(battle_.getEventSkillId(index))},
247 {"target", Value(subjectOf(battle_.getEventTarget(index)).format())},
248 });
249 events_.push_back(std::move(event));
250 }
251 return observeGameplay(session, instance);
252}
253
255 SubjectRef instance,
256 std::uint64_t afterSequence) const {
257 auto authorized = observeGameplay(session, instance);
258 if (!authorized) return Result<std::vector<GameplayEvent>>::failure(authorized.status());
259 std::move(authorized).takeValue();
260 std::vector<GameplayEvent> result;
261 for (const auto& event : events_)
262 if (event.sequence > afterSequence) result.push_back(event);
263 const bool empty = result.empty();
264 return Result<std::vector<GameplayEvent>>::success(
265 std::move(result), Status::success(empty ? StatusCode::NoOp : StatusCode::Applied));
266}
267
268} // namespace eve::rpg
LogicalId target
double value
int subject
Definition AnimSmr.cpp:163
Player-equivalent, host-neutral control adapter for one RPG Battle.
回合制战斗对象:参战者、行动顺序、目标选取、行动执行。
std::string name
size_t queued
Definition OnnxGpgpu.cpp:60
std::uint32_t targetId
bool found
std::optional< InteractionSession > session
Definition Tactics.cpp:39
Json object
float step
Definition TreeMesh.cpp:314
uint32_t index
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
const std::string & format() const noexcept
Returns the canonical namespace:name representation.
Definition Identity.h:436
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
Strong, domain-neutral reference to a runtime subject.
Definition SubjectRef.h:26
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
static SubjectRef nil() noexcept
Return the invalid nil reference.
Definition SubjectRef.h:35
std::string format() const
Return the canonical lower-case UUID spelling.
Definition SubjectRef.h:44
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
EVENGINE_API_FOUNDATION public API.
Definition Json.h:38
BattleControl(Battle &battle, SubjectRef instance)
Construct an adapter over a borrowed battle and stable instance identity.
std::vector< SubjectRef > gameplayInstances() const override
Instances currently served, in publication order.
Result< void > bindParticipant(SubjectRef subject, RPGActor *actor)
Bind a stable subject to an existing battle participant.
Result< GameplayObservation > observeGameplay(const GameplaySession &session, SubjectRef instance) const override
Observe one provider-owned gameplay instance without mutation.
Result< GameplayObservation > advanceGameplay(const GameplaySession &session, SubjectRef instance, const SimulationStep &step) override
Advance one provider-owned instance using injected deterministic time.
~BattleControl() override
Unpublish the capability without destroying the borrowed battle.
std::string_view gameplayDomain() const noexcept override
Stable provider domain used for discovery and routing.
Result< std::vector< GameplayEvent > > gameplayEvents(const GameplaySession &session, SubjectRef instance, std::uint64_t afterSequence) const override
Return events strictly newer than an instance-local sequence cursor.
Result< GameplayCommandReceipt > submitGameplay(const GameplaySession &session, SubjectRef instance, const GameplayCommand &command) override
Validate authority and atomically submit one domain command.
Result< std::vector< GameplayActionDescriptor > > availableGameplayActions(const GameplaySession &session, SubjectRef instance, SubjectRef subject) const override
Discover currently legal player-semantic actions for one subject.
一场回合制战斗(任意阵营数)。 @ownership 由调用方创建/销毁;Battle 不持有参与者的所有权(参与者为借用 ECS actor)。 @thread 在单一模拟线程上驱动整场战斗。
Definition Battle.h:54
RPGActor * getActor(int index) const
Return a participant actor by index, or null when out of range.
Definition Battle.cpp:467
void startRound()
结算所有已设行动:先攻排序、清空待行动,回合数 +1。
Definition Battle.cpp:287
void autoEnemyActions()
未设行动的 AI 侧自动选一个随机已学技能(或普攻)打随机存活敌对目标。
Definition Battle.cpp:261
bool getEventCrit(int index) const
Returns the event crit.
Definition Battle.cpp:506
double getEventAmount(int index) const
Returns the event amount.
Definition Battle.cpp:502
int getSide(int index) const
Returns the side.
Definition Battle.cpp:471
std::string getEventSkillId(int index) const
Returns the event skill id.
Definition Battle.cpp:490
RPGActor * getEventCaster(int index) const
Return the caster of a polled event, or null when out of range.
Definition Battle.cpp:494
RPGActor * getEventTarget(int index) const
Return the target of a polled event, or null when out of range.
Definition Battle.cpp:498
std::string getEventAction(int index) const
Returns the event action.
Definition Battle.cpp:486
int getTurn() const
当前回合数(1 起)。
Definition Battle.cpp:181
bool isActorAlive(RPGActor *actor) const
True when actor alive.
Definition Battle.cpp:127
eve::Result< void > setActionChecked(RPGActor *actor, const std::string &skillId, RPGActor *target=nullptr)
Validate and queue exactly one action for a living participant this round.
Definition Battle.cpp:203
void pollEvents()
Polls events.
Definition Battle.cpp:481
int getActorCount() const
Returns the actor count.
Definition Battle.cpp:466
bool executeNextAction()
执行下一条行动(compatibility facade (脚本兼容门面));返回是否执行了一条。
Definition Battle.cpp:305
bool isDefeat() const
是否战败:已结束且获胜方 != 玩家侧。
Definition Battle.cpp:179
int getEventCount() const
Returns the event count.
Definition Battle.cpp:476
bool isFinished() const
战斗是否已结束(仅剩一方存活 / 双方全灭)。
Definition Battle.cpp:177
bool isVictory() const
是否胜利:获胜方 == 玩家侧。
Definition Battle.cpp:178
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
double getCurrent(const std::string &resource)
生命等当前资源(VitalsSystem 的薄转发,max 取同名最终属性)。
Definition RPGActor.cpp:247
double getMax(const std::string &resource)
Definition RPGActor.cpp:251
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
Owning evidence that a gameplay authority accepted one command.
One player-semantic command submitted to a domain authority.
std::uint64_t expectedRevision
SimulationTick observedTick
Owning event projection safe across frames and process boundaries.
SimulationTick tick
std::uint64_t sequence
std::string causationCommandId
std::string correlationId
Immutable owning observation of one authoritative gameplay instance.
Owning authorization context shared by player, script and automation adapters.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158