载入中...
搜索中...
未找到
CardEffects.cpp
浏览该文件的文档.
1#include "card/CardEffects.h"
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <utility>
7
8namespace eve::card {
9namespace {
10
11bool hasKind(const effects::EffectInstance& effect, CardEffectKind kind) {
12 const char* name = kind == CardEffectKind::Damage ? "card:damage"
13 : kind == CardEffectKind::Heal ? "card:heal"
14 : "card:shield";
15 return effect.hasTag(name);
16}
17
18class CardSettlementPolicy final : public settlement::ISettlementPolicy {
19public:
20 explicit CardSettlementPolicy(CardEffectTarget& target) : target_(target) {}
21
22 eve::Result<void> validate(settlement::SettlementContext& context) override {
23 if (target_.health < 0 || target_.maxHealth <= 0 || target_.health > target_.maxHealth || target_.barrier < 0)
24 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card effect target state is invalid", "target"));
25 if (context.request().kind != "damage" && context.request().kind != "healing" &&
26 context.request().kind != "shield")
27 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card settlement kind is unsupported", "kind"));
29 }
30 eve::Result<void> sourceModifiers(settlement::SettlementContext&) override {
32 }
33 eve::Result<void> targetMitigation(settlement::SettlementContext&) override {
35 }
36 eve::Result<void> armorShield(settlement::SettlementContext& context) override {
37 if (context.request().kind != "damage") return eve::Result<void>::success();
38 const double absorbed = std::min(context.magnitude(), static_cast<double>(target_.barrier));
39 barrierAbsorbed_ = absorbed;
40 auto recorded = context.addAbsorbed(absorbed);
41 if (!recorded) return recorded;
42 return context.setMagnitude(context.magnitude() - absorbed);
43 }
44 eve::Result<void> clamp(settlement::SettlementContext& context) override {
45 const double maximum = context.request().kind == "damage"
46 ? target_.health
47 : context.request().kind == "healing"
48 ? target_.maxHealth - target_.health
49 : std::numeric_limits<int>::max() - target_.barrier;
50 return context.setClampMax(std::max(0.0, maximum));
51 }
52 eve::Result<settlement::PreparedApply> prepareApply(const settlement::SettlementContext& context) override {
53 const CardEffectTarget before = target_;
54 CardEffectTarget candidate = target_;
55 const int applied = static_cast<int>(context.magnitude());
56 const int absorbed = static_cast<int>(barrierAbsorbed_);
57 if (context.request().kind == "damage") {
58 candidate.barrier -= absorbed;
59 candidate.health = std::max(0, candidate.health - applied);
60 if (candidate.health == 0 && before.health > 0) ++candidate.deathTriggers;
61 } else if (context.request().kind == "healing") {
62 candidate.health = std::min(candidate.maxHealth, candidate.health + applied);
63 } else {
64 candidate.barrier += applied;
65 }
66 return eve::Result<settlement::PreparedApply>::success(settlement::PreparedApply(
67 [this, candidate]() {
68 target_ = candidate;
70 },
71 [this, before]() { target_ = before; }));
72 }
73
75 const settlement::SettlementContext& context, const settlement::SettlementResult& result) override {
76 std::vector<settlement::SettlementRequest> transitions;
77 const auto append = [&](std::string key) {
78 settlement::SettlementRequest transition = context.request();
79 transition.kind = "trigger";
80 transition.resource.clear();
81 transition.magnitude = 0.0;
82 transition.decisions.clear();
83 transition.trigger = std::move(key);
84 transition.chain = {};
85 transition.tags.push_back("trigger:" + transition.trigger);
86 transitions.push_back(std::move(transition));
87 };
88 if (target_.barrier > 0 && barrierAbsorbed_ >= static_cast<double>(target_.barrier)) append("shield_break");
89 const bool died = context.request().kind == "damage" && target_.health > 0 &&
90 result.applied >= static_cast<double>(target_.health);
91 if (died) {
92 append("death");
93 if (context.request().source.isValid() && context.request().source != context.request().target)
94 append("kill");
95 }
96 return eve::Result<std::vector<settlement::SettlementRequest>>::success(std::move(transitions));
97 }
98
99private:
100 CardEffectTarget& target_;
101 double barrierAbsorbed_ = 0.0;
102};
103
104} // namespace
105
108 auto installed = rules.install(candidate);
109 if (!installed) return installed;
110 settlement_ = std::move(candidate);
112}
113
116 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card effect target state is invalid", "target"));
117 if (container_.effectCount() != 0)
120 "card effect target cannot be reinitialized while effects are active", "target"));
121 target_ = target;
123}
124
127 const effects::EffectContainer& activeEffects) const {
128 if (target.health < 0 || target.maxHealth <= 0 || target.health > target.maxHealth || target.barrier < 0)
129 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card effect target state is invalid", "target"));
130 const bool damage = hasKind(effect, CardEffectKind::Damage);
131 const bool heal = hasKind(effect, CardEffectKind::Heal);
132 const bool shield = hasKind(effect, CardEffectKind::Shield);
133 if (!shield && ((!damage && !heal) || effect.period > 0.0)) return eve::Result<void>::success();
134 if (effect.magnitude < 0.0 || effect.magnitude > static_cast<double>(std::numeric_limits<int>::max()))
135 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card immediate magnitude is outside the target range", "magnitude"));
136 const auto parsedSubject = eve::PersistentId::parse(effect.subject);
137 if (!parsedSubject) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card shield subject is invalid", "subject"));
140 request.target = eve::SubjectRef::fromPersistentId(*parsedSubject);
141 request.kind = shield ? "shield" : heal ? "healing" : "damage";
142 request.magnitude = effect.magnitude;
143 request.tags = effect.tags;
144 CardSettlementPolicy policy(target);
145 auto activePipeline = settlement_;
146 auto activeRules = settlement::projectEffectRules(activeEffects, "card.active");
147 if (!activeRules) return eve::Result<void>::failure(activeRules.status());
148 auto installed = activeRules.value().install(activePipeline);
149 if (!installed) return installed;
150 auto settled = activePipeline.settle(request, policy);
151 if (!settled) return eve::Result<void>::failure(settled.status());
152 const auto outcome = std::move(settled).takeValue();
153 (void)outcome;
155}
156
159 const effects::EffectContainer& activeEffects) const {
160 if (target.health < 0 || target.maxHealth <= 0 || target.health > target.maxHealth || target.barrier < 0)
162 eve::DiagnosticCode::InvalidArgument, "card effect target state is invalid", "target"));
163 CardEffectUpdate result;
164 result.lifecycle = std::move(lifecycle);
165 auto activePipeline = settlement_;
166 auto activeRules = settlement::projectEffectRules(activeEffects, "card.active");
167 if (!activeRules) return eve::Result<CardEffectUpdate>::failure(activeRules.status());
168 auto installed = activeRules.value().install(activePipeline);
169 if (!installed) return eve::Result<CardEffectUpdate>::failure(installed.status());
170 for (const auto& tick : result.lifecycle.periodicTicks) {
171 if (tick.magnitude < 0.0 || tick.stackCount == 0)
173 eve::DiagnosticCode::InvalidArgument, "card periodic magnitude or stack is invalid", "periodic"));
174 const auto amount = tick.magnitude * static_cast<double>(tick.stackCount);
175 if (!std::isfinite(amount) || amount > static_cast<double>(std::numeric_limits<int>::max()))
177 eve::DiagnosticCode::InvalidArgument, "card periodic settlement overflows", "periodic"));
178 if (tick.effectId.empty())
181 "card periodic trigger has no effect identity", "periodic.effectId"));
182 const auto tagged = [&tick](const char* value) {
183 return std::find(tick.tags.begin(), tick.tags.end(), value) != tick.tags.end();
184 };
185 {
186 const auto parsedSubject = eve::PersistentId::parse(tick.subject);
187 if (!parsedSubject)
189 eve::DiagnosticCode::InvalidArgument, "card periodic subject is invalid", "periodic.subject"));
192 eve::PersistentId::fromUuid(tick.effectIdentity));
193 request.target = eve::SubjectRef::fromPersistentId(*parsedSubject);
194 request.kind = tagged("card:shield") ? "shield" : tagged("card:heal") ? "healing" : "damage";
195 request.magnitude = amount;
196 request.tags = tick.tags;
197 request.tick = tick.tick;
198 CardSettlementPolicy policy(target);
199 auto settled = activePipeline.settle(request, policy);
200 if (!settled) return eve::Result<CardEffectUpdate>::failure(settled.status());
201 const auto status = settled.status();
202 auto outcome = std::move(settled).takeValue();
203 result.absorbed += static_cast<std::uint32_t>(outcome.absorbed);
204 if (tagged("card:death-trigger") &&
205 std::any_of(outcome.derived.begin(), outcome.derived.end(),
206 [](const auto& derived) { return derived.trigger == "death"; }))
207 result.deathTriggered = true;
209 audit.index = result.settlements.size();
210 audit.request = std::move(request);
211 audit.status = status;
212 audit.result = std::move(outcome);
213 result.settlements.push_back(std::move(audit));
214 }
215 ++result.settled;
216 }
218}
219
223
226 if (definition.id.empty())
228 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card effect id must not be empty", "id"));
229 if (!subject.isValid())
231 eve::DiagnosticCode::InvalidArgument, "card effect subject must be valid", "subject"));
233 common.id = definition.id;
234 common.stackKey = definition.id;
235 common.duration = definition.duration;
236 common.period = definition.period;
237 common.magnitude = definition.magnitude;
238 common.policy = definition.policy;
239 common.payload = definition.payload;
240 common.tags = definition.tags;
241 common.tags.push_back(definition.kind == CardEffectKind::Damage ? "card:damage"
242 : definition.kind == CardEffectKind::Heal ? "card:heal"
243 : "card:shield");
244 if (definition.deathTrigger) common.tags.push_back("card:death-trigger");
245 auto valid = common.validate();
247
248 auto candidate = container_.clone();
249 auto applied = candidate.apply(common, subject.format(), definition.source);
250 if (!applied) return eve::Result<effects::EffectHandle>::failure(applied.status());
251 const std::string id = std::move(applied).takeValue();
252 auto instance = candidate.find(id);
253 if (!instance)
255 eve::DiagnosticCode::InvariantViolation, "card effect candidate disappeared", "effect"));
256 CardEffectTarget targetCandidate = target_;
257 auto immediate = executor_.applyImmediate(targetCandidate, *instance, candidate);
258 if (!immediate) return eve::Result<effects::EffectHandle>::failure(immediate.status());
259 auto handle = candidate.handleFor(id);
261 target_ = std::move(targetCandidate);
262 container_ = std::move(candidate);
263 return eve::Result<effects::EffectHandle>::success(std::move(handle).takeValue(),
265}
266
268 auto resolved = container_.resolve(handle);
269 if (!resolved) return eve::Result<void>::failure(resolved.status());
270 return container_.remove(handle.instanceId);
271}
272
274 auto candidate = container_.clone();
275 auto lifecycle = effects::EffectExecutor{}.advance(candidate, step);
276 if (!lifecycle) return eve::Result<CardEffectUpdate>::failure(lifecycle.status());
277 auto targetCandidate = target_;
278 auto settled = executor_.settle(targetCandidate, std::move(lifecycle).takeValue(), candidate);
279 if (!settled) return settled;
280 auto result = std::move(settled).takeValue();
281 target_ = std::move(targetCandidate);
282 container_ = std::move(candidate);
284}
285
286std::size_t CardEffectAdapter::count() const noexcept { return static_cast<std::size_t>(container_.effectCount()); }
287
288CardEffectSnapshot CardEffectAdapter::snapshot() const { return {container_.snapshot(), target_}; }
289
291 if (snapshotValue.target.health < 0 || snapshotValue.target.maxHealth <= 0 ||
292 snapshotValue.target.health > snapshotValue.target.maxHealth || snapshotValue.target.barrier < 0)
293 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "card effect snapshot target state is invalid", "snapshot.target"));
294 auto restored = container_.restore(snapshotValue.effects);
295 if (!restored) return restored;
296 target_ = snapshotValue.target;
298}
299
303
304} // namespace eve::card
LogicalId target
double value
int subject
Definition AnimSmr.cpp:163
Card-domain effect adapter and executor.
float maximum[3]
const GltfImportRequest & request
std::uint32_t key
wgpu::PopErrorScopeStatus status
TokenKind kind
std::string name
bool valid
std::unique_ptr< ParticleEffect > effect
PrimitiveHandle handle
SimulationTick tick
float step
Definition TreeMesh.cpp:314
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
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
eve::Result< void > restore(const CardEffectSnapshot &snapshot)
Restore a snapshot and invalidate every pre-restore effect handle.
CardEffectSnapshot snapshot() const
Capture a deep lifecycle snapshot.
std::size_t count() const noexcept
Return the number of active card effects.
eve::Result< effects::EffectHandle > apply(const CardEffectDefinition &definition, eve::SubjectRef subject)
Apply one typed card effect and return its generation-qualified handle.
eve::Result< void > initializeTarget(CardEffectTarget target)
Seed the domain target before the first effect is applied.
CardEffectTarget & target() noexcept
Return the mutable card target state owned by this adapter.
eve::Result< void > remove(effects::EffectHandle handle)
Remove one effect handle; stale handles are rejected.
eve::Result< CardEffectUpdate > advance(const eve::SimulationStep &step)
Advance and settle card effects atomically for one simulation step.
eve::Result< const effects::EffectInstance * > resolve(effects::EffectHandle handle) const
Resolve a live handle as a borrowed observation.
eve::Result< void > configureSettlementRules(const settlement::SettlementRuleSet &rules)
Replace declarative rules used by subsequent card effect settlements.
eve::Result< CardEffectUpdate > settle(CardEffectTarget &target, effects::EffectUpdateSummary lifecycle, const effects::EffectContainer &activeEffects) const
Settle all common periodic triggers using card shield/death rules.
eve::Result< void > applyImmediate(CardEffectTarget &target, const effects::EffectInstance &effect, const effects::EffectContainer &activeEffects) const
Apply immediate shield semantics to a staged target.
eve::Result< void > configureSettlementRules(const settlement::SettlementRuleSet &rules)
Replace declarative rules used by subsequent periodic settlements transactionally.
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
static constexpr Id128 fromUuid(const Id128< OtherTag > &value) noexcept
Re-tags an existing UUID at an explicit domain boundary.
Definition Identity.h:136
Owns active effect instances and their deterministic lifecycle events.
EffectContainer snapshot() const
Capture an independent lifecycle snapshot for staging/restore.
eve::Result< const EffectInstance * > resolve(EffectHandle handle) const
Resolve a generation-qualified handle for this call.
eve::Result< void > remove(const std::string &id, const std::string &reason="removed")
Removes one active instance and emits a Removed event.
EffectContainer clone() const
Create an independent deep copy suitable for ECS staging.
int effectCount() const
Returns active instance count in deterministic creation order.
eve::Result< void > restore(const EffectContainer &snapshot)
Restore a previously captured snapshot as one observable commit.
Executes lifecycle time progression for an EffectContainer.
eve::Result< EffectUpdateSummary > advance(EffectContainer &container, double dtSeconds) const
Advances one container by a legacy seconds delta.
Deterministically ordered, composable settlement pipeline.
Definition Settlement.h:542
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.
CardEffectKind
Card-specific interpretation of an effect magnitude.
Definition CardEffects.h:29
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
eve::Result< SettlementRuleSet > projectEffectRules(const effects::EffectContainer &effects, std::string_view scope)
Project active effect payloads into an owning settlement rule snapshot.
void validate(const RuntimeTensor &v)
Validate.
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158
Strong card definition projected into the common effect definition.
Definition CardEffects.h:40
Serializable in-memory card effect snapshot.
Definition CardEffects.h:65
effects::EffectContainer effects
Definition CardEffects.h:66
Mutable card combat state changed by the card effect executor.
Definition CardEffects.h:32
Result of one card effect lifecycle step and its domain settlement.
Definition CardEffects.h:55
effects::EffectUpdateSummary lifecycle
Definition CardEffects.h:56
std::vector< settlement::SettlementBatchItemResult > settlements
Owning canonical audits for periodic settlements in lifecycle order.
Definition CardEffects.h:61
Immutable-by-convention template used to create effect instances.
std::vector< std::string > tags
double period
Period in seconds for domain executor settlement; zero disables periodic ticks.
eve::Result< void > validate() const
Validates definition fields without changing any state.
Generation-qualified local reference to a canonical effect instance.
One owned runtime effect fact.
Summary returned by the lifecycle executor after one simulation step.
std::vector< EffectPeriodicTick > periodicTicks
Periodic triggers in deterministic container order.
Indexed owning outcome for one independently committed settlement item.
Definition Settlement.h:265
std::optional< SettlementResult > result
Definition Settlement.h:269
Domain-neutral input to one settlement operation.
Definition Settlement.h:107