载入中...
搜索中...
未找到
Battle.cpp
浏览该文件的文档.
1#include "rpg/Battle.h"
2
4#include "rpg/BattleSystem.h"
5#include "rpg/RPGActor.h"
7#include "rpg/SkillSystem.h"
8#include "rpg/StatusSystem.h"
9#include "rpg/TraitSystem.h"
10#include "rpg/VitalsSystem.h"
11
12#include <algorithm>
13#include <array>
14
15namespace eve::rpg {
16
17void Battle::addActor(RPGActor* actor, int side) {
18 if (!actor) return;
19 std::array<std::uint8_t, 16> bytes{0x52, 0x50, 0x47, 0x42, 0x41, 0x54, 0x54, 0x4c,
20 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
21 const auto sequence = nextSettlementSubject_++;
22 for (std::size_t index = 0; index < sizeof(sequence); ++index)
23 bytes[15 - index] = static_cast<std::uint8_t>((sequence >> (index * 8u)) & 0xffu);
24 participants_.push_back(Participant{actor, side, SubjectRef::fromPersistentId(PersistentId(bytes))});
25}
26
29 auto installed = rules.install(candidate);
30 if (!installed) return installed;
31 settlement_ = std::move(candidate);
33}
34
37 const std::string rootResource = request.resource;
38 const auto actorForSubject = [&](const SubjectRef& subject) -> RPGActor* {
39 const auto found = std::find_if(participants_.begin(), participants_.end(),
40 [&](const Participant& value) { return value.settlementSubject == subject; });
41 return found == participants_.end() ? nullptr : found->actor;
42 };
44 [&](const settlement::SettlementRequest& chainRequest)
46 RPGActor* chainTarget = actorForSubject(chainRequest.target);
47 RPGActor* chainSource = actorForSubject(chainRequest.source);
48 if (chainTarget == nullptr)
50 eve::DiagnosticCode::NotFound, "RPG settlement chain target is not a battle participant", "target"));
51
52 auto normalized = chainRequest;
53 RPGSettlementAdapter::Config chainConfig;
54 chainConfig.healthAttribute = normalized.resource.empty() ? rootResource : normalized.resource;
55 chainConfig.maxHealthAttribute = chainConfig.healthAttribute;
56 chainConfig.emitLifeTransitions = chainConfig.healthAttribute == "hp";
57 Value::Object chainContext;
58 if (const auto* supplied = normalized.context.getIf<Value::Object>()) chainContext = *supplied;
59 chainContext.try_emplace("source_multiplier", 1.0);
60 chainContext.try_emplace("critical", false);
61 chainContext.try_emplace("element", std::string("damage"));
62 const double chainTargetMaximum = VitalsSystem::getMax(chainTarget, chainConfig.healthAttribute);
63 chainContext["target_hp_ratio"] =
64 chainTargetMaximum > 0.0
65 ? VitalsSystem::getCurrent(chainTarget, chainConfig.healthAttribute) / chainTargetMaximum
66 : 0.0;
67 const double chainSourceMaximum =
68 chainSource == nullptr ? 0.0 : VitalsSystem::getMax(chainSource, chainConfig.healthAttribute);
69 chainContext["source_hp_ratio"] =
70 chainSourceMaximum > 0.0
71 ? VitalsSystem::getCurrent(chainSource, chainConfig.healthAttribute) / chainSourceMaximum
72 : 0.0;
73 normalized.context = Value(std::move(chainContext));
74 RPGSettlementAdapter chainAdapter(*chainTarget, normalized.target, chainSource, normalized.source,
75 std::move(chainConfig));
76 auto chainPipeline = settlement_;
77 if (chainSource != nullptr) {
78 auto sourceRules = settlement::projectEffectRules(chainSource->statuses()->container, "rpg.source");
79 if (!sourceRules) return eve::Result<settlement::SettlementResult>::failure(sourceRules.status());
80 auto installed = sourceRules.value().install(chainPipeline);
81 if (!installed) return eve::Result<settlement::SettlementResult>::failure(installed.status());
82 }
83 auto targetRules = settlement::projectEffectRules(chainTarget->statuses()->container, "rpg.target");
84 if (!targetRules) return eve::Result<settlement::SettlementResult>::failure(targetRules.status());
85 auto installed = targetRules.value().install(chainPipeline);
86 if (!installed) return eve::Result<settlement::SettlementResult>::failure(installed.status());
87 return chainPipeline.settle(normalized, chainAdapter);
88 };
89 return settlement_.settleChain(request, executeSettlement, 8, 64);
90}
91
93 const StatusTickEvent& tick, SubjectRef source, SimulationTick simulationTick) {
94 const SubjectRef target = settlementSubjectOf(tick.actor);
95 auto request = makeStatusTickSettlementRequest(tick, std::move(source), target, simulationTick);
97 auto settled = settleRequest(request.value());
98 if (!settled) return settled;
99 for (const auto& outcome : settled.value()) {
100 if (!outcome.result) {
101 events_.push_back(BattleEvent{"settlementFailed", tick.effectId, nullptr, tick.actor, 0.0, false});
102 break;
103 }
104 RPGActor* eventTarget = nullptr;
105 RPGActor* eventSource = nullptr;
106 for (const auto& participant : participants_) {
107 if (participant.settlementSubject == outcome.request.target) eventTarget = participant.actor;
108 if (participant.settlementSubject == outcome.request.source) eventSource = participant.actor;
109 }
110 if (eventTarget == nullptr) continue;
111 if (outcome.request.kind == "trigger") {
112 events_.push_back(BattleEvent{outcome.request.trigger, tick.effectId, eventSource, eventTarget, 0.0, false});
113 continue;
114 }
115 const bool healing = outcome.request.kind == "heal" || outcome.request.kind == "healing";
116 const char* action = healing ? "heal" : "damage";
117 VitalsSystem::publishSettledChange(eventTarget, outcome.request.resource, action, outcome.result->applied,
118 tick.effectId);
119 events_.push_back(BattleEvent{action, tick.effectId, eventSource, eventTarget, outcome.result->applied,
120 !healing && outcome.result->critical});
121 }
122 return settled;
123}
124
125bool Battle::isDead(const Participant& p) const { return !p.actor || VitalsSystem::isDead(p.actor, "hp"); }
126
127bool Battle::isActorAlive(RPGActor* actor) const { return actor && !VitalsSystem::isDead(actor, "hp"); }
128
129std::vector<RPGActor*> Battle::livingOnSide(int side) const {
130 std::vector<RPGActor*> out;
131 for (const auto& p : participants_) {
132 if (p.side == side && !isDead(p)) out.push_back(p.actor);
133 }
134 return out;
135}
136
137RPGActor* Battle::randomOpponent(int mySide) {
138 std::vector<RPGActor*> opps;
139 for (const auto& p : participants_) {
140 if (p.side != mySide && !isDead(p)) opps.push_back(p.actor);
141 }
142 if (opps.empty()) return nullptr;
143 return opps[size_t((seedCounter_++) % opps.size())];
144}
145
146int Battle::sideOf(RPGActor* actor) const {
147 for (const auto& p : participants_) {
148 if (p.actor == actor) return p.side;
149 }
150 return playerSide_;
151}
152
153SubjectRef Battle::settlementSubjectOf(RPGActor* actor) const noexcept {
154 const auto found = std::find_if(participants_.begin(), participants_.end(),
155 [actor](const Participant& value) { return value.actor == actor; });
156 return found == participants_.end() ? SubjectRef::nil() : found->settlementSubject;
157}
158
159int Battle::computeWinnerSide() const {
160 int aliveSide = -1;
161 bool anyAlive = false;
162 for (const auto& p : participants_) {
163 if (isDead(p)) continue;
164 anyAlive = true;
165 if (aliveSide == -1) {
166 aliveSide = p.side;
167 } else if (aliveSide != p.side) {
168 return -1; // 多侧尚存活 → 未定
169 }
170 }
171 return anyAlive ? aliveSide : -1; // 全灭 → -1(平局)
172}
173
174void Battle::setPlayerSide(int side) { playerSide_ = side; }
175int Battle::getPlayerSide() const { return playerSide_; }
176
177bool Battle::isFinished() const { return finished_; }
178bool Battle::isVictory() const { return finished_ && winner_ == playerSide_; }
179bool Battle::isDefeat() const { return finished_ && winner_ != -1 && winner_ != playerSide_; }
180int Battle::getWinnerSide() const { return winner_; }
181int Battle::getTurn() const { return turn_; }
182
183void Battle::setAction(RPGActor* actor, const std::string& skillId, RPGActor* target) {
184 setActionChecked(actor, skillId, target).ignore();
185}
186
187RPGActor* Battle::lowestHealthTarget(int side, bool sameSide) const {
188 RPGActor* selected = nullptr;
189 double selectedRatio = 0.0;
190 for (const auto& participant : participants_) {
191 if (isDead(participant) || ((participant.side == side) != sameSide)) continue;
192 const double maximum = VitalsSystem::getMax(participant.actor, "hp");
193 if (maximum <= 0.0) continue;
194 const double ratio = VitalsSystem::getCurrent(participant.actor, "hp") / maximum;
195 if (!selected || ratio < selectedRatio) {
196 selected = participant.actor;
197 selectedRatio = ratio;
198 }
199 }
200 return selected;
201}
202
204 const auto reject = [](eve::DiagnosticCode code, std::string message, std::string path) {
206 eve::Diagnostic::error(code, message, path, {}, "rpg.battle.action"));
207 };
208 if (!actor) return reject(eve::DiagnosticCode::InvalidArgument, "actor must not be null", "actor");
209 if (finished_ || started_)
210 return reject(eve::DiagnosticCode::Conflict, "actions can only be queued before a non-finished round",
211 "battle");
212 const auto participant = std::find_if(participants_.begin(), participants_.end(),
213 [actor](const Participant& value) { return value.actor == actor; });
214 if (participant == participants_.end())
215 return reject(eve::DiagnosticCode::NotFound, "actor is not a battle participant", "actor");
216 if (isDead(*participant)) return reject(eve::DiagnosticCode::Conflict, "dead actors cannot queue actions", "actor");
217 if (std::any_of(roundActions_.begin(), roundActions_.end(),
218 [actor](const PendingAction& value) { return value.actor == actor; }))
219 return reject(eve::DiagnosticCode::Conflict, "actor already has an action queued for this round", "actor");
220 if (!skillId.empty() && !actor->knowsSkill(skillId))
221 return reject(eve::DiagnosticCode::NotFound, "actor does not know the requested skill", "skillId");
222 if (target) {
223 const auto targetParticipant =
224 std::find_if(participants_.begin(), participants_.end(),
225 [target](const Participant& value) { return value.actor == target; });
226 if (targetParticipant == participants_.end())
227 return reject(eve::DiagnosticCode::NotFound, "target is not a battle participant", "target");
228 if (isDead(*targetParticipant)) return reject(eve::DiagnosticCode::Conflict, "target is not alive", "target");
229 const std::string targetType = skillId.empty() ? "enemySingle" : SkillSystem::getTargetType(actor, skillId);
230 if (targetType == "self" && target != actor)
231 return reject(eve::DiagnosticCode::InvalidArgument, "self-target skill must target its caster", "target");
232 if (targetType == "allySingle" && targetParticipant->side != participant->side)
233 return reject(eve::DiagnosticCode::InvalidArgument, "ally-target action must target the caster side",
234 "target");
235 if (targetType != "self" && targetType != "allySingle" && targetParticipant->side == participant->side)
236 return reject(eve::DiagnosticCode::InvalidArgument, "enemy-target action must target another side",
237 "target");
238 }
239 roundActions_.push_back(PendingAction{actor, skillId, target, 0.0});
241}
242
244 BattleTargetPolicy policy) {
245 if (policy == BattleTargetPolicy::Auto) return setActionChecked(actor, skillId, nullptr);
246 const int side = sideOf(actor);
247 RPGActor* target = nullptr;
248 if (policy == BattleTargetPolicy::Self)
249 target = actor;
250 else if (policy == BattleTargetPolicy::LowestHealthAlly)
251 target = lowestHealthTarget(side, true);
252 else if (policy == BattleTargetPolicy::LowestHealthEnemy)
253 target = lowestHealthTarget(side, false);
254 if (!target)
256 "target policy found no living participant", "policy",
257 {}, "rpg.battle.action-policy"));
258 return setActionChecked(actor, skillId, target);
259}
260
262 if (finished_) return;
263 for (const auto& p : participants_) {
264 if (p.side == playerSide_ || isDead(p)) continue;
265 bool already = false;
266 for (const auto& ra : roundActions_) {
267 if (ra.actor == p.actor) {
268 already = true;
269 break;
270 }
271 }
272 if (already) continue;
273 // 随机已学技能,否则普攻(skillId 空 = 普攻)
274 std::vector<std::string> known;
275 for (const auto& [id, unused] : p.actor->skills()->known) {
276 (void)unused;
277 known.push_back(id);
278 }
279 std::string skillId;
280 if (!known.empty()) {
281 skillId = known[size_t((seedCounter_++) % known.size())];
282 }
283 roundActions_.push_back(PendingAction{p.actor, skillId, randomOpponent(p.side), 0.0});
284 }
285}
286
288 if (finished_) return;
289 ++turn_;
290 // 计算先攻:speed 属性 + 技能攻击速度修正。
291 for (auto& pa : roundActions_) {
292 double speed = AttributeSystem::getFinal(pa.actor, "speed");
293 if (speed <= 0.0) speed = AttributeSystem::getFinal(pa.actor, "agi");
294 if (speed <= 0.0) speed = 0.0;
295 pa.initiative = speed + TraitSystem::getAttackSpeed(pa.actor);
296 }
297 std::stable_sort(roundActions_.begin(), roundActions_.end(),
298 [](const PendingAction& x, const PendingAction& y) { return x.initiative > y.initiative; });
299 queue_ = std::move(roundActions_);
300 roundActions_.clear();
301 started_ = true;
302 events_.push_back(BattleEvent{"roundStart", "", nullptr, nullptr, 0.0, false});
303}
304
306 if (!started_ || finished_) return false;
307 if (queue_.empty()) {
308 started_ = false;
309 return false;
310 }
311 PendingAction pa = queue_.front();
312 queue_.erase(queue_.begin());
313
314 // 已死则跳过
315 if (VitalsSystem::isDead(pa.actor, "hp")) {
316 return true;
317 }
318
319 // 解析目标:未指定则按 targetType / 默认对手。
320 RPGActor* target = pa.target;
321 if (!target) {
322 if (pa.skillId.empty()) {
323 target = randomOpponent(sideOf(pa.actor));
324 } else if (SkillSystem::getTargetType(pa.actor, pa.skillId) == "self") {
325 target = pa.actor;
326 } else if (SkillSystem::getTargetType(pa.actor, pa.skillId) == "allySingle") {
327 target = lowestHealthTarget(sideOf(pa.actor), true);
328 } else {
329 target = randomOpponent(sideOf(pa.actor));
330 }
331 }
332 if (!target) return true;
333
334 execute(pa, seedCounter_);
335 // 每步检查结束:仅剩一方 → 胜;双方全灭 → 平;否则继续。
336 const int w = computeWinnerSide();
337 if (w != -1) {
338 finished_ = true;
339 winner_ = w;
340 events_.push_back(BattleEvent{winner_ == playerSide_ ? "victory" : "defeat", "", nullptr, nullptr, 0.0, false});
341 } else {
342 bool anyAlive = false;
343 for (const auto& p : participants_) {
344 if (!isDead(p)) {
345 anyAlive = true;
346 break;
347 }
348 }
349 if (!anyAlive) {
350 finished_ = true;
351 winner_ = -1;
352 events_.push_back(BattleEvent{"defeat", "", nullptr, nullptr, 0.0, false});
353 }
354 }
355 return true;
356}
357
358void Battle::execute(PendingAction& pa, unsigned& seedCounter) {
359 const std::string skillId = pa.skillId;
360 const std::string targetType =
361 skillId.empty() ? std::string("enemySingle") : SkillSystem::getTargetType(pa.actor, skillId);
362 RPGActor* target = pa.target;
363 if (!target) {
364 if (targetType == "self") {
365 target = pa.actor;
366 } else if (targetType == "allySingle") {
367 target = lowestHealthTarget(sideOf(pa.actor), true);
368 } else {
369 target = randomOpponent(sideOf(pa.actor));
370 }
371 }
372 if (!target) return;
373
374 events_.push_back(BattleEvent{"actionStart", skillId, pa.actor, target, 0.0, false});
375
376 // 目标已死:换一个存活对手
377 if (targetType != "self" && VitalsSystem::isDead(target, "hp")) {
378 target =
379 targetType == "allySingle" ? lowestHealthTarget(sideOf(pa.actor), true) : randomOpponent(sideOf(pa.actor));
380 if (!target) return;
381 }
382
383 const SkillDamageSpec* spec = skillId.empty() ? nullptr : BattleSystem::findSkillDamage(skillId);
384 if (!spec && !skillId.empty()) {
385 // 无伤害规格:走技能授予效果(治疗/状态等,无公式伤害)。
386 SkillSystem::beginCast(pa.actor, skillId, target);
387 return;
388 }
389 if (!spec) {
390 // 普攻默认规格
391 static const SkillDamageSpec kDefault{};
392 spec = &kDefault;
393 }
394
395 DamageResult result = BattleSystem::resolveHit(pa.actor, target, *spec, seedCounter++);
396 if (!result.hit) {
397 events_.push_back(BattleEvent{"miss", skillId, pa.actor, target, 0.0, false});
398 return;
399 }
400
401 // 目标资源名任意:damageType 即资源名,"XHeal" 后缀表示治疗。
402 std::string resource = spec->damageType;
403 bool heal = false;
404 if (resource.size() >= 4 && resource.compare(resource.size() - 4, 4, "Heal") == 0) {
405 resource = resource.substr(0, resource.size() - 4);
406 heal = true;
407 }
408 const SubjectRef sourceRef = settlementSubjectOf(pa.actor);
409 const SubjectRef targetRef = settlementSubjectOf(target);
410 settlement::SettlementRequest request;
411 request.source = sourceRef;
412 request.target = targetRef;
413 request.kind = heal ? "healing" : "damage";
414 request.resource = resource;
415 request.magnitude = result.amount;
416 request.tags = {heal ? "rpg:healing" : "rpg:damage", "element:" + spec->element};
417 request.tick = SimulationTick(static_cast<std::uint64_t>(turn_));
419 context["source_multiplier"] = 1.0;
420 context["critical"] = result.crit;
421 context["critical_multiplier"] = 1.0;
422 context["element"] = spec->element.empty() ? std::string("damage") : spec->element;
423 const double sourceMaximum = VitalsSystem::getMax(pa.actor, resource);
424 const double targetMaximum = VitalsSystem::getMax(target, resource);
425 context["source_hp_ratio"] = sourceMaximum > 0.0 ? VitalsSystem::getCurrent(pa.actor, resource) / sourceMaximum
426 : 0.0;
427 context["target_hp_ratio"] = targetMaximum > 0.0 ? VitalsSystem::getCurrent(target, resource) / targetMaximum
428 : 0.0;
429 request.context = Value(std::move(context));
430 auto settled = settleRequest(request);
431 if (!settled || settled.value().empty() || !settled.value().front().result) {
432 events_.push_back(BattleEvent{"settlementFailed", skillId, pa.actor, target, 0.0, false});
433 return;
434 }
435 auto outcomes = std::move(settled).takeValue();
436 for (const auto& outcome : outcomes) {
437 if (!outcome.result) {
438 events_.push_back(BattleEvent{"settlementFailed", outcome.request.trigger, pa.actor, target, 0.0, false});
439 break;
440 }
441 RPGActor* eventTarget = nullptr;
442 RPGActor* eventSource = nullptr;
443 for (const auto& participant : participants_) {
444 if (participant.settlementSubject == outcome.request.target) eventTarget = participant.actor;
445 if (participant.settlementSubject == outcome.request.source) eventSource = participant.actor;
446 }
447 if (eventTarget == nullptr) continue;
448 if (outcome.request.kind == "trigger") {
449 events_.push_back(BattleEvent{outcome.request.trigger, outcome.request.trigger, eventSource, eventTarget,
450 0.0, false});
451 continue;
452 }
453 const bool outcomeHeal = outcome.request.kind == "heal" || outcome.request.kind == "healing";
454 const std::string action = outcomeHeal ? "heal" : "damage";
455 const std::string cause = outcome.request.trigger.empty()
456 ? (outcomeHeal || skillId.empty() ? std::string{} : skillId)
457 : outcome.request.trigger;
458 VitalsSystem::publishSettledChange(eventTarget, outcome.request.resource, action, outcome.result->applied,
459 cause);
460 events_.push_back(BattleEvent{action, outcome.request.trigger.empty() ? skillId : outcome.request.trigger,
461 eventSource, eventTarget, outcome.result->applied,
462 !outcomeHeal && outcome.result->critical});
463 }
464}
465
466int Battle::getActorCount() const { return int(participants_.size()); }
468 if (index < 0 || size_t(index) >= participants_.size()) return nullptr;
469 return participants_[size_t(index)].actor;
470}
471int Battle::getSide(int index) const {
472 if (index < 0 || size_t(index) >= participants_.size()) return 0;
473 return participants_[size_t(index)].side;
474}
475
476int Battle::getEventCount() const { return int(polled_.size()); }
478 if (index < 0 || size_t(index) >= polled_.size()) return BattleEvent{};
479 return polled_[size_t(index)];
480}
482 polled_ = std::move(events_);
483 events_.clear();
484}
485
486std::string Battle::getEventAction(int index) const {
487 if (index < 0 || size_t(index) >= polled_.size()) return {};
488 return polled_[size_t(index)].action;
489}
490std::string Battle::getEventSkillId(int index) const {
491 if (index < 0 || size_t(index) >= polled_.size()) return {};
492 return polled_[size_t(index)].skillId;
493}
495 if (index < 0 || size_t(index) >= polled_.size()) return nullptr;
496 return polled_[size_t(index)].caster;
497}
499 if (index < 0 || size_t(index) >= polled_.size()) return nullptr;
500 return polled_[size_t(index)].target;
501}
502double Battle::getEventAmount(int index) const {
503 if (index < 0 || size_t(index) >= polled_.size()) return 0.0;
504 return polled_[size_t(index)].amount;
505}
507 if (index < 0 || size_t(index) >= polled_.size()) return false;
508 return polled_[size_t(index)].crit;
509}
510
511} // namespace eve::rpg
LogicalId target
double value
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int subject
Definition AnimSmr.cpp:163
RPG's thin compatibility facade over the attributes module.
伤害公式 DSL 与技能伤害注册。
回合制战斗对象:参战者、行动顺序、目标选取、行动执行。
glm::vec4 p[6]
std::string message
DiagnosticCode code
float maximum[3]
const GltfImportRequest & request
std::uint64_t bytes
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::string action
Definition PlayHost.cpp:117
std::string path
Definition PlayHost.cpp:110
bool found
std::string resource
std::string element
SimulationTick tick
ecs::EntityHandle side
特征系统:施加/移除运行时特征,并提供耐性/特殊参数/攻击附加等查询。
uint32_t index
const UnitySourceAsset & source
const VegetationPresetContext & context
当前资源(hp/mana...)的受伤 / 治疗 / 复活 / 死亡。
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
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Definition Result.h:537
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
static double getFinal(RPGActor *actor, const std::string &attribute)
Compute the final value through the canonical attributes engine.
static DamageResult resolveHit(RPGActor *attacker, RPGActor *target, const SkillDamageSpec &spec, unsigned seed)
结算一次命中:命中判定、公式、元素耐性、暴击。
static const SkillDamageSpec * findSkillDamage(const std::string &skillId)
Finds skill damage.
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
eve::Result< void > configureSettlementRules(const settlement::SettlementRuleSet &rules)
Replace declarative buff/effect rules used by subsequent battle settlements.
Definition Battle.cpp:27
void addActor(RPGActor *actor, int side)
加入参战者;side 为任意阵营 id。
Definition Battle.cpp:17
void setPlayerSide(int side)
玩家阵营 id(isVictory/isDefeat 依据),默认 0。
Definition Battle.cpp:174
int getPlayerSide() const
Returns the player side.
Definition Battle.cpp:175
void autoEnemyActions()
未设行动的 AI 侧自动选一个随机已学技能(或普攻)打随机存活敌对目标。
Definition Battle.cpp:261
bool getEventCrit(int index) const
Returns the event crit.
Definition Battle.cpp:506
eve::Result< std::vector< settlement::SettlementBatchItemResult > > settleStatusTick(const StatusTickEvent &tick, SubjectRef source, SimulationTick simulationTick)
Execute one emitted periodic status tick through this battle's canonical settlement path.
Definition Battle.cpp:92
void setAction(RPGActor *actor, const std::string &skillId, RPGActor *target=nullptr)
为参战者设置本回合行动;target 为空由目标规则自动解析。
Definition Battle.cpp:183
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
int getWinnerSide() const
获胜阵营 id;未结束或平局(双方全灭)返回 -1。
Definition Battle.cpp:180
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
BattleEvent getEvent(int index) const
Returns the event.
Definition Battle.cpp:477
int getActorCount() const
Returns the actor count.
Definition Battle.cpp:466
eve::Result< void > setActionByPolicyChecked(RPGActor *actor, const std::string &skillId, BattleTargetPolicy policy)
Resolve a deterministic target policy and queue one checked action atomically.
Definition Battle.cpp:243
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
bool knowsSkill(const std::string &skillId)
Definition RPGActor.cpp:199
EVENGINE_API_PLATFORM public API.
Definition SkillSystem.h:23
static std::string getTargetType(RPGActor *actor, const std::string &skillId)
技能的目标类型字符串("" 表示未学会/未知)。
static bool beginCast(RPGActor *actor, const std::string &skillId, RPGActor *target=nullptr, std::string *reason=nullptr)
尝试释放技能:通过 canCast 检查后立即扣除消耗并进入冷却; castTime<=0 时立即结算(授予效果 + 产生 SkillCastEvent), 否则进入读条状态,由 update() 在读...
static double getAttackSpeed(RPGActor *actor)
攻击速度修正(加值)。
static double getCurrent(RPGActor *actor, const std::string &resource)
当前值(无 vitals/资源时 0)。
static double getMax(RPGActor *actor, const std::string &resource)
上限:取 actor 同名最终属性(getFinalAttribute),无则 0。
static void publishSettledChange(RPGActor *actor, const std::string &resource, const std::string &action, double amount, const std::string &source="")
Publish resource events for a state change already committed by Settlement.
static bool isDead(RPGActor *actor, const std::string &resource)
是否死亡:max > 0 且 current <= 0。
Deterministically ordered, composable settlement pipeline.
Definition Settlement.h:542
std::function< eve::Result< SettlementResult >(const SettlementRequest &)> RequestExecutor
Executes one owning chain request using its request-specific policy and rule snapshot.
Definition Settlement.h:546
eve::Result< std::vector< SettlementBatchItemResult > > settleChain(const SettlementRequest &root, const RequestExecutor &execute, std::uint32_t maxDepth, std::uint32_t maxSettlements) const
Settle a root request and its policy-produced derived requests in stable breadth-first order.
Validated immutable-style rule collection installable into pipelines.
eve::Result< void > install(SettlementPipeline &pipeline) const
Install a stable snapshot as custom stages on a pipeline.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
BattleTargetPolicy
Deterministic target-selection policies for queued battle actions.
Definition Battle.h:36
eve::Result< settlement::SettlementRequest > makeStatusTickSettlementRequest(const StatusTickEvent &tick, SubjectRef source, SubjectRef target, SimulationTick simulationTick)
Project one configured RPG status tick into the canonical settlement request.
::eve::SubjectRef SubjectRef
Strong reference to a runtime subject.
Definition Targeting.h:76
eve::Result< SettlementRuleSet > projectEffectRules(const effects::EffectContainer &effects, std::string_view scope)
Project active effect payloads into an owning settlement rule snapshot.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
StrongUuid< detail::PersistentIdTag > PersistentId
Stable instance identity for persistence, networking and process boundaries.
Definition Identity.h:254
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
RPG AttributeSet policy adapter for the common settlement pipeline.
一次战斗反馈事件。
Definition Battle.h:39
周期性状态触发的 tick 事件。周期效果(period > 0)不会自动修改属性, 而是每个周期产生一个 tick 事件交给上层(脚本或 C++ 结算系统)处理—— 这样伤害/治疗类周期效果可以完整地...
Definition StatusTypes.h:69
Domain-neutral input to one settlement operation.
Definition Settlement.h:107