载入中...
搜索中...
未找到
AttributeSet.cpp
浏览该文件的文档.
2
4#include "common/Capability.h"
6
7#include <algorithm>
8#include <cmath>
9#include <utility>
10
11namespace eve::attributes {
12
13namespace {
14
15eve::Diagnostic invalidArgument(std::string message, std::string path = {}) {
17}
18
19} // namespace
20
22 switch (operation) {
23 case AttributeOperation::Add: return "add";
24 case AttributeOperation::AdditivePercent: return "additive_percent";
25 case AttributeOperation::MultiplicativePercent: return "multiplicative_percent";
26 case AttributeOperation::Override: return "override";
27 case AttributeOperation::ClampMin: return "clamp_min";
28 case AttributeOperation::ClampMax: return "clamp_max";
29 case AttributeOperation::Custom: return "custom";
30 }
31 return "unknown";
32}
33
36 if (operation == "add") {
38 } else if (operation == "additive_percent" || operation == "mulAdd") {
40 } else if (operation == "multiplicative_percent" || operation == "mulMul") {
42 } else if (operation == "multiply") {
44 value -= 1.0;
45 } else if (operation == "override") {
47 } else if (operation == "clamp_min" || operation == "clampMin" || operation == "min") {
49 } else if (operation == "clamp_max" || operation == "clampMax" || operation == "max") {
51 } else {
53 invalidArgument("unknown attribute operation", std::string(operation)));
54 }
55 parsed.value = value;
56 return eve::Result<ParsedAttributeOperation>::success(std::move(parsed));
57}
58
60 if (!policyId.empty() && function) operations_[policyId] = std::move(function);
61}
62
63void AttributeOperationRegistry::unregisterOperation(const std::string& policyId) { operations_.erase(policyId); }
64
65bool AttributeOperationRegistry::has(const std::string& policyId) const {
66 return operations_.find(policyId) != operations_.end();
67}
68
69double AttributeOperationRegistry::apply(const std::string& policyId, double current, double value) const {
70 const auto it = operations_.find(policyId);
71 return it == operations_.end() ? current : it->second(current, value);
72}
73
77
79
80AttributeModifier::AttributeModifier(ModifierId modifierId, SourceId sourceId, std::string_view operationName,
81 double modifierValue, ModifierPriority priorityValue)
82 : id(std::move(modifierId)), source(std::move(sourceId)), value(modifierValue), priority(priorityValue) {
83 if (operationName == "add") {
85 } else if (operationName == "mulAdd") {
87 } else if (operationName == "mulMul") {
89 } else if (operationName == "multiply") {
91 value -= 1.0;
92 } else if (operationName == "override") {
94 } else if (operationName == "min" || operationName == "clampMin") {
96 } else if (operationName == "max" || operationName == "clampMax") {
98 } else {
100 policyId = std::string(operationName);
101 }
102}
103
104double computeAttributeValue(const AttributeValue& attribute, const AttributeOperationRegistry* customOperations) {
105 double additive = 0.0;
106 double additivePercent = 0.0;
107 double multiplicativePercent = 1.0;
108 std::vector<const AttributeModifier*> ordered;
109 ordered.reserve(attribute.modifiers.size());
110
111 for (const auto& modifier : attribute.modifiers) {
112 switch (modifier.operation) {
113 case AttributeOperation::Add: additive += modifier.value; break;
114 case AttributeOperation::AdditivePercent: additivePercent += modifier.value; break;
115 case AttributeOperation::MultiplicativePercent: multiplicativePercent *= 1.0 + modifier.value; break;
119 case AttributeOperation::Custom: ordered.push_back(&modifier); break;
120 }
121 }
122
123 double result = (attribute.base + additive) * (1.0 + additivePercent);
124 result *= multiplicativePercent;
125
126 std::stable_sort(ordered.begin(), ordered.end(), [](const auto* left, const auto* right) {
127 if (left->priority != right->priority) return left->priority < right->priority;
128 return left->sequence < right->sequence;
129 });
130
131 for (const auto* modifier : ordered) {
132 switch (modifier->operation) {
133 case AttributeOperation::Override: result = modifier->value; break;
134 case AttributeOperation::ClampMin: result = std::max(result, modifier->value); break;
135 case AttributeOperation::ClampMax: result = std::min(result, modifier->value); break;
137 if (customOperations && customOperations->has(modifier->policyId))
138 result = customOperations->apply(modifier->policyId, result, modifier->value);
139 break;
143 }
144 }
145 return result;
146}
147
148void AttributeSet::publishWorldAdapter() {
149 if (worldAdapter_) return;
150 worldAdapter_ = std::make_unique<AttributeSetStateAdapter>(*this);
151 eve::cap::addListener<eve::IStateQuery>(static_cast<eve::IStateQuery*>(worldAdapter_.get()));
152 eve::cap::addListener<eve::IStateMutation>(static_cast<eve::IStateMutation*>(worldAdapter_.get()));
153}
154
155void AttributeSet::withdrawWorldAdapter() {
156 if (!worldAdapter_) return;
157 eve::cap::removeListener<eve::IStateQuery>(static_cast<eve::IStateQuery*>(worldAdapter_.get()));
158 eve::cap::removeListener<eve::IStateMutation>(static_cast<eve::IStateMutation*>(worldAdapter_.get()));
159 worldAdapter_.reset();
160}
161
162AttributeSet::AttributeSet(std::string subject) : subject_(std::move(subject)) { publishWorldAdapter(); }
163
164AttributeSet::~AttributeSet() { withdrawWorldAdapter(); }
165
167 : subject_(other.subject_),
168 values_(other.values_),
169 nextSequence_(other.nextSequence_),
170 nextModifierId_(other.nextModifierId_) {
171 publishWorldAdapter();
172}
173
175 if (this == &other) return *this;
176 subject_ = other.subject_;
177 values_ = other.values_;
178 nextSequence_ = other.nextSequence_;
179 nextModifierId_ = other.nextModifierId_;
180 order_.clear();
181 orderDirty_ = true;
182 if (!worldAdapter_) publishWorldAdapter();
183 return *this;
184}
185
187 : subject_(std::move(other.subject_)),
188 values_(std::move(other.values_)),
189 nextSequence_(other.nextSequence_),
190 nextModifierId_(other.nextModifierId_) {
191 other.withdrawWorldAdapter();
192 publishWorldAdapter();
193}
194
196 if (this == &other) return *this;
197 withdrawWorldAdapter();
198 subject_ = std::move(other.subject_);
199 values_ = std::move(other.values_);
200 nextSequence_ = other.nextSequence_;
201 nextModifierId_ = other.nextModifierId_;
202 order_.clear();
203 orderDirty_ = true;
204 other.withdrawWorldAdapter();
205 publishWorldAdapter();
206 return *this;
207}
208
209const std::string& AttributeSet::subject() const { return subject_; }
210
211void AttributeSet::setBase(const AttributeId& attribute, double value) {
212 if (!attribute.empty()) {
213 auto& state = values_[attribute];
214 state.base = value;
215 state.dirty = true;
216 }
217}
218
219void AttributeSet::modifyBase(const AttributeId& attribute, double delta) {
220 if (!attribute.empty()) {
221 auto& state = values_[attribute];
222 state.base += delta;
223 state.dirty = true;
224 }
225}
226
227bool AttributeSet::has(const AttributeId& attribute) const {
228 return !attribute.empty() && values_.find(attribute) != values_.end();
229}
230
231double AttributeSet::getBase(const AttributeId& attribute, double fallback) const {
232 const auto it = values_.find(attribute);
233 return it == values_.end() ? fallback : it->second.base;
234}
235
236double AttributeSet::getFinal(const AttributeId& attribute, double fallback,
237 const AttributeOperationRegistry* customOperations) const {
238 const auto it = values_.find(attribute);
239 if (it == values_.end()) return fallback;
240 auto& state = it->second;
241 if (customOperations) return computeAttributeValue(state, customOperations);
242 if (state.dirty) {
244 state.dirty = false;
245 }
246 return state.cached;
247}
248
249bool AttributeSet::isValidOperation(const AttributeModifier& modifier) noexcept {
250 switch (modifier.operation) {
256 case AttributeOperation::ClampMax: return true;
257 case AttributeOperation::Custom: return !modifier.policyId.empty();
258 }
259 return false;
260}
261
263 if (modifier.attribute.empty())
265 invalidArgument("attribute modifier requires an attribute id", "attribute"));
266 if (!isValidOperation(modifier))
268 invalidArgument("attribute modifier has an invalid operation", "operation"));
269 if (!std::isfinite(modifier.value))
270 return eve::Result<ModifierId>::failure(invalidArgument("attribute modifier value must be finite", "value"));
271
272 if (modifier.id.empty()) {
273 do {
274 modifier.id = "attribute:modifier:";
275 if (!subject_.empty()) modifier.id += subject_ + ":";
276 modifier.id += std::to_string(nextModifierId_++);
277 } while ([&] {
278 for (const auto& [attribute, state] : values_)
279 for (const auto& existing : state.modifiers)
280 if (existing.id == modifier.id) return true;
281 return false;
282 }());
283 }
284 const ModifierId modifierId = modifier.id;
285 const AttributeId attributeId = modifier.attribute;
286 removeModifier(modifierId).ignore("replace existing attribute modifier");
287 modifier.sequence = nextSequence_++;
288 auto& state = values_[attributeId];
289 state.modifiers.push_back(std::move(modifier));
290 state.dirty = true;
291 orderDirty_ = true;
293}
294
295bool AttributeSet::addModifier(const std::string& id, const std::string& attribute, const std::string& source,
296 const std::string& operation, double value, ModifierPriority priority) {
297 if (id.empty()) return false;
299 if (!parsed.ok()) return false;
300 AttributeModifier modifier{id, attribute, source, parsed.value().operation, parsed.value().value, priority};
301 auto result = addModifier(std::move(modifier));
302 return result.ok();
303}
304
306 if (id.empty()) return eve::Result<void>::failure(invalidArgument("modifier id must not be empty", "id"));
307 for (auto& [attribute, state] : values_) {
308 const auto it = std::find_if(state.modifiers.begin(), state.modifiers.end(),
309 [&](const auto& modifier) { return modifier.id == id; });
310 if (it != state.modifiers.end()) {
311 state.modifiers.erase(it);
312 state.dirty = true;
313 orderDirty_ = true;
315 }
316 }
319 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "attribute modifier was not found", "id")));
320}
321
323 if (attribute.empty())
324 return eve::Result<void>::failure(invalidArgument("attribute id must not be empty", "attribute"));
325 if (id.empty()) return eve::Result<void>::failure(invalidArgument("modifier id must not be empty", "id"));
326 const auto valueIt = values_.find(attribute);
327 if (valueIt == values_.end())
330 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "attribute modifier was not found", "id")));
331 auto& modifiers = valueIt->second.modifiers;
332 const auto modifierIt =
333 std::find_if(modifiers.begin(), modifiers.end(), [&](const auto& modifier) { return modifier.id == id; });
334 if (modifierIt == modifiers.end())
337 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "attribute modifier was not found", "id")));
338 modifiers.erase(modifierIt);
339 valueIt->second.dirty = true;
340 orderDirty_ = true;
342}
343
345 if (source.empty()) return eve::Result<int>::failure(invalidArgument("source id must not be empty", "source"));
346 int removed = 0;
347 for (auto& [attributeId, state] : values_) {
348 if (!attribute.empty() && attributeId != attribute) continue;
349 const auto oldSize = state.modifiers.size();
350 state.modifiers.erase(std::remove_if(state.modifiers.begin(), state.modifiers.end(),
351 [&](const auto& modifier) { return modifier.source == source; }),
352 state.modifiers.end());
353 if (state.modifiers.size() != oldSize) {
354 state.dirty = true;
355 removed += static_cast<int>(oldSize - state.modifiers.size());
356 }
357 }
358 if (removed != 0) orderDirty_ = true;
361}
362
363void AttributeSet::invalidate(const AttributeId& attribute) {
364 const auto it = values_.find(attribute);
365 if (it != values_.end()) it->second.dirty = true;
366}
367
369 for (auto& [attribute, state] : values_) {
370 state.modifiers.clear();
371 state.dirty = true;
372 }
373 order_.clear();
374 orderDirty_ = false;
375}
376
378 int count = 0;
379 for (const auto& [attribute, state] : values_) count += static_cast<int>(state.modifiers.size());
380 return count;
381}
382
383void AttributeSet::rebuildOrder() const {
384 if (!orderDirty_) return;
385 order_.clear();
386 for (const auto& [attribute, state] : values_)
387 for (const auto& modifier : state.modifiers) order_.push_back(&modifier);
388 std::sort(order_.begin(), order_.end(),
389 [](const auto* left, const auto* right) { return left->sequence < right->sequence; });
390 orderDirty_ = false;
391}
392
394 rebuildOrder();
395 if (index < 0 || static_cast<std::size_t>(index) >= order_.size()) return nullptr;
396 return order_[static_cast<std::size_t>(index)];
397}
398
399int AttributeSet::attributeCount() const { return static_cast<int>(values_.size()); }
400
401std::string AttributeSet::attributeAt(int index) const {
402 if (index < 0 || static_cast<std::size_t>(index) >= values_.size()) return {};
403 std::vector<std::string> names;
404 names.reserve(values_.size());
405 for (const auto &[name, value] : values_) {
406 (void)value;
407 names.push_back(name);
408 }
409 std::sort(names.begin(), names.end());
410 return names[static_cast<std::size_t>(index)];
411}
412
413} // namespace eve::attributes
ActionParameterOperation operation
double value
int subject
Definition AnimSmr.cpp:163
int priority
std::uint64_t sourceId
std::string message
DiagnosticCode code
HexVec3 left
HexVec3 right
std::string name
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
int removed
MigrationFunction function
double current
std::uint32_t count
Consumer-owned query and mutation contracts for authoritative world state.
std::string policyId
uint32_t index
const UnitySourceAsset & source
AttributeSet implementation of the common world-state contracts.
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
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 failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Registry for explicitly named custom attribute policies.
bool has(const std::string &policyId) const
Return whether a policy is registered.
void registerOp(const std::string &policyId, Function function)
Compatibility spelling for RPG's former custom-op facade.
double apply(const std::string &policyId, double current, double value) const
Apply a policy, returning current unchanged when it is absent.
void unregisterOperation(const std::string &policyId)
Remove a named policy.
std::function< double(double current, double value)> Function
void unregisterOp(const std::string &policyId)
Compatibility spelling for RPG's former custom-op facade.
void registerOperation(const std::string &policyId, Function function)
Register or replace a named policy; calls execute on the caller thread.
Generic owning collection of canonical attributes and modifiers.
double getFinal(const AttributeId &attribute, double fallback=0.0, const AttributeOperationRegistry *customOperations=nullptr) const
Return the deterministic final value after all modifiers.
std::string attributeAt(int index) const
Return an attribute id in deterministic lexical order, or an empty string.
int attributeCount() const
Number of named attribute values, including modifier-only values.
~AttributeSet()
Withdraw world-state capability listeners.
eve::Result< ModifierId > addModifier(AttributeModifier modifier)
Add or replace a canonical modifier and return its stable id.
void modifyBase(const AttributeId &attribute, double delta)
Add delta to a base value, creating it from zero when absent.
void setBase(const AttributeId &attribute, double value)
Set or replace a base value. Empty attribute ids are ignored for compatibility.
void invalidate(const AttributeId &attribute)
Mark one attribute dirty after an external policy or restore change.
int modifierCount() const
Number of modifiers.
void clearModifiers()
Remove all modifiers.
AttributeSet & operator=(const AttributeSet &other)
Copy a set while rebuilding its internal borrowed enumeration view.
const std::string & subject() const
Stable game-defined id of the entity or object owning this set.
AttributeSet(std::string subject={})
Construct a set associated with an optional stable subject id.
const AttributeModifier * modifierAt(int index) const
Return a modifier by deterministic sequence order, or null when out of range.
double getBase(const AttributeId &attribute, double fallback=0.0) const
Return a base value, or fallback when absent.
eve::Result< int > removeBySource(const SourceId &source, const AttributeId &attribute={})
Remove source modifiers with a structured result containing the count.
bool has(const AttributeId &attribute) const
Return whether a base value or modifier names the attribute.
eve::Result< void > removeModifier(const ModifierId &id)
Remove one modifier with a structured result.
All-or-nothing mutation boundary owned by the consumer.
Read-only world-state boundary owned by consumers such as Dialogue.
Definition StateAccess.h:79
std::string AttributeId
std::string SourceId
double computeAttributeValue(const AttributeValue &attribute, const AttributeOperationRegistry *customOperations)
Compute one attribute using the canonical operation order.
AttributeOperation
Built-in calculation operation for an attribute modifier.
const char * attributeOperationName(AttributeOperation operation) noexcept
Stable spelling of a built-in attribute operation. @ownership The returned string is a borrowed,...
std::int32_t ModifierPriority
eve::Result< ParsedAttributeOperation > parseAttributeOperation(std::string_view operation, double value)
Parse a built-in operation spelling used by existing data and APIs.
std::string ModifierId
One canonical runtime modifier applied to one named attribute.
AttributeModifier()=default
Attribute modifier.
Runtime value and modifiers for exactly one attribute.
std::vector< AttributeModifier > modifiers
Parsed legacy operation and its normalized value.