载入中...
搜索中...
未找到
Damage.cpp
浏览该文件的文档.
1#include "combat/Damage.h"
2
3#include "tags/GameplayTag.h"
4
5#include <algorithm>
6#include <cmath>
7#include <utility>
8
9namespace eve::combat {
10namespace {
11
12bool finiteNonNegative(double value) { return std::isfinite(value) && value >= 0.0; }
13
14Result<void> validateAmounts(const DamageAmounts& amounts) {
15 if (!finiteNonNegative(amounts.healthDamage)) return Result<void>::failure(
16 Diagnostic::error(DiagnosticCode::InvalidArgument, "Resolved health damage is invalid", "healthDamage"));
17 if (!finiteNonNegative(amounts.poiseDamage)) return Result<void>::failure(
18 Diagnostic::error(DiagnosticCode::InvalidArgument, "Resolved poise damage is invalid", "poiseDamage"));
19 if (!finiteNonNegative(amounts.knockbackScale))
21 Diagnostic::error(DiagnosticCode::InvalidArgument, "Resolved knockback scale is invalid", "knockbackScale"));
22 return Result<void>::success();
23}
24
25class DamageSettlementPolicy final : public settlement::ISettlementPolicy {
26public:
27 DamageSettlementPolicy(CombatState& target, const DamageRequest& request, DamageAmounts amounts,
28 const HitReactionPolicy& reactionPolicy)
29 : target_(target), request_(request), reactionPolicy_(reactionPolicy), amounts_(std::move(amounts)) {}
30
31 Result<void> validate(settlement::SettlementContext&) override {
32 auto policyValid = reactionPolicy_.validate();
33 if (!policyValid) return policyValid;
34 return Result<void>::success();
35 }
36
37 Result<void> sourceModifiers(settlement::SettlementContext& context) override {
38 return context.setMagnitude(amounts_.healthDamage);
39 }
40 Result<void> targetMitigation(settlement::SettlementContext& context) override {
41 const double before = context.magnitude();
42 const double after = before * request_.incomingDamageMultiplier;
43 if (after < before) {
44 auto resisted = context.addResisted(before - after);
45 if (!resisted) return resisted;
46 }
47 return context.setMagnitude(after);
48 }
49 Result<void> armorShield(settlement::SettlementContext& context) override {
50 shieldAbsorbed_ = std::min(context.magnitude(), request_.availableShield);
51 auto recorded = context.addAbsorbed(shieldAbsorbed_);
52 if (!recorded) return recorded;
53 return context.setMagnitude(context.magnitude() - shieldAbsorbed_);
54 }
55 Result<void> clamp(settlement::SettlementContext& context) override { return context.setClampMax(target_.health); }
56
57 Result<settlement::PreparedApply> prepareApply(const settlement::SettlementContext& context) override {
58 CombatState candidate = target_;
59 candidate.health = std::max(0.0, candidate.health - context.magnitude());
60 candidate.poise = std::max(0.0, candidate.poise - amounts_.poiseDamage);
61 const CombatState before = target_;
62 return Result<settlement::PreparedApply>::success(settlement::PreparedApply(
63 [this, candidate]() {
64 target_ = candidate;
66 },
67 [this, before]() { target_ = before; }));
68 }
69
70 Result<std::vector<settlement::SettlementRequest>> prepareTrigger(
71 const settlement::SettlementContext& context, const settlement::SettlementResult& result) override {
72 std::vector<settlement::SettlementRequest> transitions;
73 const auto append = [&](std::string key) {
74 settlement::SettlementRequest transition = context.request();
75 transition.kind = "trigger";
76 transition.resource.clear();
77 transition.magnitude = 0.0;
78 transition.decisions.clear();
79 transition.trigger = std::move(key);
80 transition.chain = {};
81 transition.tags.push_back("trigger:" + transition.trigger);
82 transitions.push_back(std::move(transition));
83 };
84
85 if (request_.availableShield > 0.0 && shieldAbsorbed_ >= request_.availableShield)
86 append("shield_break");
87 const bool died = target_.health > 0.0 && result.applied >= target_.health;
88 if (died) {
89 append("death");
90 if (context.request().source.isValid() && context.request().source != context.request().target)
91 append("kill");
92 }
93 return Result<std::vector<settlement::SettlementRequest>>::success(std::move(transitions));
94 }
95
96 [[nodiscard]] const DamageAmounts& amounts() const noexcept { return amounts_; }
97 [[nodiscard]] double shieldAbsorbed() const noexcept { return shieldAbsorbed_; }
98
99private:
100 CombatState& target_;
101 const DamageRequest& request_;
102 const HitReactionPolicy& reactionPolicy_;
103 DamageAmounts amounts_;
104 double shieldAbsorbed_ = 0.0;
105};
106
107class HealingSettlementPolicy final : public settlement::ISettlementPolicy {
108public:
109 explicit HealingSettlementPolicy(CombatState& target) : target_(target) {}
110
111 Result<void> validate(settlement::SettlementContext&) override { return target_.validate(); }
112 Result<void> sourceModifiers(settlement::SettlementContext&) override { return Result<void>::success(); }
113 Result<void> targetMitigation(settlement::SettlementContext&) override { return Result<void>::success(); }
114 Result<void> armorShield(settlement::SettlementContext&) override { return Result<void>::success(); }
115 Result<void> clamp(settlement::SettlementContext& context) override {
116 return context.setClampMax(target_.maxHealth - target_.health);
117 }
118 Result<settlement::PreparedApply> prepareApply(const settlement::SettlementContext& context) override {
119 const double before = target_.health;
120 const double candidate = before + context.magnitude();
121 return Result<settlement::PreparedApply>::success(settlement::PreparedApply(
122 [this, candidate]() {
123 target_.health = candidate;
125 },
126 [this, before]() { target_.health = before; }));
127 }
128 Result<std::vector<settlement::SettlementRequest>> prepareTrigger(
129 const settlement::SettlementContext&, const settlement::SettlementResult&) override {
130 return Result<std::vector<settlement::SettlementRequest>>::success({});
131 }
132
133private:
134 CombatState& target_;
135};
136
137} // namespace
138
141 Diagnostic::error(DiagnosticCode::InvalidArgument, "Combat subject is invalid", "subject"));
142 if (!finiteNonNegative(maxHealth) || !finiteNonNegative(health) || health > maxHealth)
144 Diagnostic::error(DiagnosticCode::InvalidArgument, "Combat health is outside [0,maxHealth]", "health"));
145 if (!finiteNonNegative(maxPoise) || !finiteNonNegative(poise) || poise > maxPoise)
147 Diagnostic::error(DiagnosticCode::InvalidArgument, "Combat poise is outside [0,maxPoise]", "poise"));
148 return Result<void>::success();
149}
150
152 if (!target.isValid()) return Result<void>::failure(
153 Diagnostic::error(DiagnosticCode::InvalidArgument, "Damage target is invalid", "target"));
155 Diagnostic::error(DiagnosticCode::InvalidArgument, "Damage type must be a gameplay tag", "damageType"));
156 if (!finiteNonNegative(healthDamage)) return Result<void>::failure(
157 Diagnostic::error(DiagnosticCode::InvalidArgument, "Health damage is invalid", "healthDamage"));
158 if (!finiteNonNegative(poiseDamage)) return Result<void>::failure(
159 Diagnostic::error(DiagnosticCode::InvalidArgument, "Poise damage is invalid", "poiseDamage"));
160 if (!finiteNonNegative(incomingDamageMultiplier))
162 Diagnostic::error(DiagnosticCode::InvalidArgument, "Incoming damage multiplier is invalid", "incomingDamageMultiplier"));
163 if (!finiteNonNegative(availableShield)) return Result<void>::failure(
164 Diagnostic::error(DiagnosticCode::InvalidArgument, "Available shield is invalid", "availableShield"));
165 if (!std::isfinite(knockback.x) || !std::isfinite(knockback.y) || !std::isfinite(knockback.z))
167 Diagnostic::error(DiagnosticCode::InvalidArgument, "Knockback impulse must be finite", "knockback"));
168 return Result<void>::success();
169}
170
172 if (!finiteNonNegative(flinchDamageThreshold))
174 Diagnostic::error(DiagnosticCode::InvalidArgument, "Flinch threshold is invalid", "flinchDamageThreshold"));
175 if (!std::isfinite(staggerPoiseFraction) || staggerPoiseFraction < 0.0 || staggerPoiseFraction > 1.0)
177 Diagnostic::error(DiagnosticCode::InvalidArgument, "Stagger poise fraction must be in [0,1]", "staggerPoiseFraction"));
178 return Result<void>::success();
179}
180
182 auto stateValid = target.validate();
183 if (!stateValid) return Result<DamageAmounts>::failure(stateValid.status());
184 auto requestValid = request.validate();
185 if (!requestValid) return Result<DamageAmounts>::failure(requestValid.status());
186 if (target.subject != request.target)
188 DiagnosticCode::InvalidArgument, "Damage request target does not match combat state", "target"));
189 DamageAmounts amounts{request.healthDamage, request.poiseDamage, 1.0};
190 if (rule_) {
191 auto evaluated = rule_->evaluate(request, target);
192 if (!evaluated) return Result<DamageAmounts>::failure(evaluated.status());
193 amounts = std::move(evaluated).takeValue();
194 }
195 auto valid = validateAmounts(amounts);
196 if (!valid) return Result<DamageAmounts>::failure(valid.status());
197 return Result<DamageAmounts>::success(amounts);
198}
199
201 auto previewed = preview(target, request);
202 if (!previewed) return Result<DamageOutcome>::failure(previewed.status());
203 const CombatState before = target;
204 DamageSettlementPolicy adapter(target, request, std::move(previewed).takeValue(), policy_);
205 settlement::SettlementRequest settlementRequest;
206 settlementRequest.source = request.source;
207 settlementRequest.target = request.target;
208 settlementRequest.kind = "damage";
209 settlementRequest.magnitude = request.healthDamage;
210 settlementRequest.tags = {request.damageType};
212 context["damage_type"] = request.damageType;
213 context["poise_damage"] = request.poiseDamage;
214 context["incoming_damage_multiplier"] = request.incomingDamageMultiplier;
215 context["available_shield"] = request.availableShield;
216 context["target_health_ratio"] =
217 target.maxHealth > 0.0 ? target.health / target.maxHealth : 0.0;
218 context["target_poise_ratio"] = target.maxPoise > 0.0 ? target.poise / target.maxPoise : 0.0;
219 settlementRequest.context = Value(std::move(context));
220 auto settled = settlement_.settle(settlementRequest, adapter);
221 if (!settled) return Result<DamageOutcome>::failure(settled.status());
222 const auto settlementResult = std::move(settled).takeValue();
223 const DamageAmounts amounts = adapter.amounts();
224 const double appliedHealth = before.health - target.health;
225 const double appliedPoise = before.poise - target.poise;
226
228 if (target.health == 0.0 && before.health > 0.0) {
229 reaction = HitReaction::Death;
230 } else if (target.poise == 0.0 && appliedPoise > 0.0) {
231 reaction = HitReaction::Knockdown;
232 } else if (before.maxPoise > 0.0 && appliedPoise >= before.maxPoise * policy_.staggerPoiseFraction) {
233 reaction = HitReaction::Stagger;
234 } else if (appliedHealth > 0.0 && appliedHealth >= policy_.flinchDamageThreshold) {
235 reaction = HitReaction::Flinch;
236 }
237
239 request.target,
240 before.health,
241 target.health,
242 before.poise,
243 target.poise,
244 appliedHealth,
245 appliedPoise,
246 adapter.shieldAbsorbed(),
247 {request.knockback.x * amounts.knockbackScale, request.knockback.y * amounts.knockbackScale,
248 request.knockback.z * amounts.knockbackScale},
249 reaction,
251 std::move(settlementRequest),
252 std::move(settlementResult)};
254}
255
257 const DamageRequest& request) const {
258 CombatState candidate = target;
259 return apply(candidate, request);
260}
261
263 double amount) const {
264 auto valid = target.validate();
266 if (!finiteNonNegative(amount))
268 Diagnostic::error(DiagnosticCode::InvalidArgument, "Healing amount is invalid", "amount"));
270 request.source = std::move(source);
271 request.target = target.subject;
272 request.kind = "heal";
273 request.resource = "health";
274 request.magnitude = amount;
275 HealingSettlementPolicy policy(target);
276 return settlement_.settle(request, policy);
277}
278
281 auto installed = rules.install(candidate);
282 if (!installed) return installed;
283 settlement_ = std::move(candidate);
285}
286
288 auto valid = target.validate();
289 if (!valid) return Result<double>::failure(valid.status());
290 if (!finiteNonNegative(amount))
292 Diagnostic::error(DiagnosticCode::InvalidArgument, "Poise recovery amount is invalid", "amount"));
293 const double recovered = std::min(target.maxPoise, target.poise + amount);
294 const bool changed = recovered != target.poise;
295 target.poise = recovered;
297}
298
299} // namespace eve::combat
LogicalId target
double value
Deterministic damage, poise, hit-reaction and knockback resolution.
Stable hierarchical gameplay-tag definitions and registry.
const GltfImportRequest & request
std::uint32_t key
bool valid
std::string_view adapter
const UnitySourceAsset & source
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
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
bool isValid() const noexcept
Return whether this reference is valid and non-nil.
Definition SubjectRef.h:38
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
Result< void > configureSettlementRules(const settlement::SettlementRuleSet &rules)
Replace the optional declarative buff/effect rules used by subsequent damage settlements.
Definition Damage.cpp:279
Result< DamageOutcome > previewSettlement(const CombatState &target, const DamageRequest &request) const
Preview the complete rule, mitigation, shield, clamp and reaction result without mutation.
Definition Damage.cpp:256
Result< DamageAmounts > preview(const CombatState &target, const DamageRequest &request) const
Resolve provider damage amounts before settlement stages, without mutating the target.
Definition Damage.cpp:181
Result< double > recoverPoise(CombatState &target, double amount) const
Restore an explicit poise amount, clamped to maxPoise.
Definition Damage.cpp:287
Result< settlement::SettlementResult > heal(CombatState &target, SubjectRef source, double amount) const
Resolve and atomically commit health restoration through the configured settlement pipeline.
Definition Damage.cpp:262
Result< DamageOutcome > apply(CombatState &target, const DamageRequest &request) const
Resolve and atomically commit one damage request.
Definition Damage.cpp:200
virtual Result< DamageAmounts > evaluate(const DamageRequest &request, const CombatState &target) const =0
Resolve final non-negative amounts without mutating target state.
Deterministically ordered, composable settlement pipeline.
Definition Settlement.h:542
eve::Result< SettlementResult > settle(const SettlementRequest &request, ISettlementPolicy &policy, game_event::GameEventLog *events=nullptr) const
Execute one settlement transaction against a domain policy.
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.
HitReaction
Typed reaction selected after health and poise are resolved.
Definition Damage.h:26
bool isValidGameplayTagName(std::string_view name) noexcept
Returns whether a name is a canonical dot-separated gameplay tag.
void validate(const RuntimeTensor &v)
Validate.
Mutable combat state owned by the gameplay domain for one subject.
Definition Damage.h:37
Result< void > validate() const
Validate finite ranges and the non-nil subject link.
Definition Damage.cpp:139
Provider result prepared before the authoritative state is mutated.
Definition Damage.h:67
Complete owning audit result of one committed damage transaction.
Definition Damage.h:98
Owning input for one damage transaction.
Definition Damage.h:49
std::string damageType
Definition Damage.h:53
Result< void > validate() const
Validate target, canonical gameplay-tag damage type and finite amounts.
Definition Damage.cpp:151
double availableShield
Shield balance available to this transaction; the caller commits reported consumption.
Definition Damage.h:59
double incomingDamageMultiplier
Target-owned effect multiplier applied in the target-mitigation settlement stage.
Definition Damage.h:57
Result< void > validate() const
Validate finite non-negative thresholds and fraction in [0,1].
Definition Damage.cpp:171
Domain-neutral input to one settlement operation.
Definition Settlement.h:107
std::vector< std::string > tags
Definition Settlement.h:116