载入中...
搜索中...
未找到
SettlementAdapter.cpp
浏览该文件的文档.
2
4#include "rpg/Effect.h"
5#include "rpg/VitalsSystem.h"
6
7#include <algorithm>
8#include <cmath>
9#include <limits>
10#include <memory>
11#include <set>
12#include <string>
13#include <utility>
14
15namespace eve::rpg {
16namespace {
17
20}
21
22bool isDamageKind(std::string_view kind) noexcept { return kind == "damage"; }
23
24bool isHealingKind(std::string_view kind) noexcept { return kind == "heal" || kind == "healing"; }
25
26eve::Result<double> checkedNumber(double value, std::string_view name) {
27 if (!std::isfinite(value))
29 eve::DiagnosticCode::InvariantViolation, std::string(name) + " must be finite", std::string(name)));
31}
32
34 std::string path) {
37}
38
41}
42
43const eve::Value* objectField(const eve::Value::Object& object, std::string_view name) {
44 const auto found = object.find(std::string(name));
45 return found == object.end() ? nullptr : &found->second;
46}
47
48} // namespace
49
52 if (tick.actor == nullptr || tick.effectId.empty() || tick.instanceId <= 0 || tick.stacks <= 0)
53 return requestFailure(eve::DiagnosticCode::InvalidArgument, "RPG status tick identity is invalid", "tick");
54 if (!target.isValid())
55 return requestFailure(eve::DiagnosticCode::InvalidArgument, "RPG status tick target must be valid", "target");
56 const EffectDefinition* definition = EffectRegistry::find(tick.effectId);
57 if (definition == nullptr)
58 return requestFailure(eve::DiagnosticCode::NotFound, "RPG status tick definition was not found",
59 "tick.effectId");
60 const auto encoded = definition->extra.find("settlement.tick");
61 if (encoded == definition->extra.end())
62 return requestFailure(eve::DiagnosticCode::NotFound, "RPG effect has no settlement.tick configuration",
63 "effect.extra.settlement.tick");
64 auto parsed = eve::Value::fromJson(encoded->second);
65 if (!parsed) return requestFailure(parsed.status());
66 const auto* object = parsed.value().getIf<eve::Value::Object>();
67 if (object == nullptr)
68 return requestFailure(eve::DiagnosticCode::InvalidArgument, "settlement.tick must be a JSON object",
69 "effect.extra.settlement.tick");
70 static const std::set<std::string> fields = {"context", "kind", "magnitude", "resource", "tags"};
71 for (const auto& [name, value] : *object) {
72 (void)value;
73 if (!fields.contains(name))
74 return requestFailure(eve::DiagnosticCode::InvalidArgument, "unknown settlement.tick field",
75 "effect.extra.settlement.tick." + name);
76 }
77 const auto* kindValue = objectField(*object, "kind");
78 const auto* resourceValue = objectField(*object, "resource");
79 const auto* magnitudeValue = objectField(*object, "magnitude");
80 const auto* kind = kindValue == nullptr ? nullptr : kindValue->getIf<std::string>();
81 const auto* resource = resourceValue == nullptr ? nullptr : resourceValue->getIf<std::string>();
82 if (kind == nullptr || kind->empty() || resource == nullptr || resource->empty() || magnitudeValue == nullptr)
83 return requestFailure(eve::DiagnosticCode::InvalidArgument,
84 "settlement.tick requires kind, resource, and magnitude",
85 "effect.extra.settlement.tick");
86 double magnitude = 0.0;
87 if (const auto* real = magnitudeValue->getIf<double>())
88 magnitude = *real;
89 else if (const auto* integer = magnitudeValue->getIf<std::int64_t>())
90 magnitude = static_cast<double>(*integer);
91 else
92 return requestFailure(eve::DiagnosticCode::InvalidArgument, "settlement.tick magnitude must be a number",
93 "effect.extra.settlement.tick.magnitude");
94 magnitude *= static_cast<double>(tick.stacks);
95 if (!std::isfinite(magnitude) || magnitude < 0.0)
96 return requestFailure(eve::DiagnosticCode::InvalidArgument,
97 "settlement.tick magnitude must be finite and non-negative",
98 "effect.extra.settlement.tick.magnitude");
99
102 request.target = target;
103 request.kind = *kind;
104 request.resource = *resource;
105 request.magnitude = magnitude;
106 request.tick = simulationTick;
107 request.tags = definition->tags;
108 request.tags.push_back("rpg:status-tick");
109 if (const auto* tagsValue = objectField(*object, "tags")) {
110 const auto* tags = tagsValue->getIf<eve::Value::Array>();
111 if (tags == nullptr)
112 return requestFailure(eve::DiagnosticCode::InvalidArgument, "settlement.tick tags must be an array",
113 "effect.extra.settlement.tick.tags");
114 for (std::size_t index = 0; index < tags->size(); ++index) {
115 const auto* tag = (*tags)[index].getIf<std::string>();
116 if (tag == nullptr || tag->empty())
117 return requestFailure(eve::DiagnosticCode::InvalidArgument,
118 "settlement.tick tag must be a non-empty string",
119 "effect.extra.settlement.tick.tags[" + std::to_string(index) + "]");
120 request.tags.push_back(*tag);
121 }
122 }
123 std::sort(request.tags.begin(), request.tags.end());
124 request.tags.erase(std::unique(request.tags.begin(), request.tags.end()), request.tags.end());
126 if (const auto* contextValue = objectField(*object, "context")) {
127 const auto* configured = contextValue->getIf<eve::Value::Object>();
128 if (configured == nullptr)
129 return requestFailure(eve::DiagnosticCode::InvalidArgument, "settlement.tick context must be an object",
130 "effect.extra.settlement.tick.context");
131 context = *configured;
132 }
133 context["effect_id"] = tick.effectId;
134 context["status_instance_id"] = static_cast<std::int64_t>(tick.instanceId);
135 context["stacks"] = static_cast<std::int64_t>(tick.stacks);
136 request.context = eve::Value(std::move(context));
138}
139
142
144 : target_(target), targetRef_(targetRef), config_(std::move(config)) {}
145
147 SubjectRef sourceRef)
148 : RPGSettlementAdapter(target, std::move(targetRef), source, std::move(sourceRef), Config{}) {}
149
151 SubjectRef sourceRef, Config config)
152 : target_(target), targetRef_(targetRef), source_(source), sourceRef_(sourceRef), config_(std::move(config)) {}
153
154bool RPGSettlementAdapter::isDamage(const settlement::SettlementContext& context) const noexcept {
155 return isDamageKind(context.request().kind);
156}
157
158bool RPGSettlementAdapter::isHealing(const settlement::SettlementContext& context) const noexcept {
159 return isHealingKind(context.request().kind);
160}
161
162const eve::Value* RPGSettlementAdapter::contextValue(const settlement::SettlementContext& context,
163 std::string_view key) noexcept {
164 const auto* object = context.request().context.getIf<eve::Value::Object>();
165 if (object == nullptr) return nullptr;
166 const auto it = object->find(std::string(key));
167 return it == object->end() ? nullptr : &it->second;
168}
169
170std::optional<double> RPGSettlementAdapter::numberValue(const eve::Value* value) noexcept {
171 if (value == nullptr) return std::nullopt;
172 if (const auto* number = value->getIf<double>()) return *number;
173 if (const auto* integer = value->getIf<std::int64_t>()) return static_cast<double>(*integer);
174 return std::nullopt;
175}
176
177std::optional<bool> RPGSettlementAdapter::boolValue(const eve::Value* value) noexcept {
178 if (value == nullptr) return std::nullopt;
179 if (const auto* boolean = value->getIf<bool>()) return *boolean;
180 return std::nullopt;
181}
182
183std::optional<std::string> RPGSettlementAdapter::stringValue(const eve::Value* value) {
184 if (value == nullptr) return std::nullopt;
185 if (const auto* string = value->getIf<std::string>()) return *string;
186 return std::nullopt;
187}
188
189eve::Result<double> RPGSettlementAdapter::readBase(std::string_view name) const {
190 if (name.empty())
192 eve::DiagnosticCode::InvalidArgument, "RPG settlement attribute name must not be empty", "attribute"));
193 const auto& attributes = target_.attributes()->values;
194 if (!attributes.has(std::string(name)))
196 eve::DiagnosticCode::NotFound, "RPG settlement attribute is not present", std::string(name)));
197 return checkedNumber(attributes.getBase(std::string(name)), name);
198}
199
200eve::Result<double> RPGSettlementAdapter::readFinal(std::string_view name, double fallback) const {
201 if (name.empty()) return eve::Result<double>::success(fallback);
202 const auto& attributes = target_.attributes()->values;
203 if (!attributes.has(std::string(name))) return eve::Result<double>::success(fallback);
204 return checkedNumber(attributes.getFinal(std::string(name), fallback), name);
205}
206
207eve::Result<double> RPGSettlementAdapter::readSourceFinal(std::string_view name, double fallback) const {
208 if (source_ == nullptr || !sourceRef_.isValid()) return eve::Result<double>::success(fallback);
209 const auto& attributes = source_->attributes()->values;
210 if (!attributes.has(std::string(name))) return eve::Result<double>::success(fallback);
211 return checkedNumber(attributes.getFinal(std::string(name), fallback), name);
212}
213
215 if (!(context.request().target == targetRef_))
217 eve::DiagnosticCode::Conflict, "RPG settlement request target does not match adapter target", "target"));
218 const bool damage = isDamage(context);
219 const bool healing = isHealing(context);
220 const bool trigger = context.request().kind == "trigger";
221 if (!damage && !healing && !trigger)
223 eve::DiagnosticCode::Unsupported, "RPG settlement adapter supports damage, healing and trigger kinds only",
224 "kind"));
225
226 auto health = readBase(config_.healthAttribute);
227 const bool healthOk = health.ok();
228 if (!healthOk) {
229 const auto status = health.status();
231 }
232 auto maximum = readBase(config_.maxHealthAttribute);
233 const bool maximumOk = maximum.ok();
234 if (!maximumOk) {
235 const auto status = maximum.status();
237 }
238 if (health.value() > maximum.value())
240 eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "RPG health exceeds max health", "health"));
241 context.setStageDetail("policy", "rpg");
242 context.setStageDetail("health_attribute", config_.healthAttribute);
243 context.setStageDetail("kind", context.request().kind);
244 (void)damage;
246}
247
249 if (context.request().kind == "trigger") return context.setMagnitude(0.0);
250 double multiplier = 1.0;
251 if (const auto value = numberValue(contextValue(context, "source_multiplier"))) {
252 multiplier = *value;
253 } else {
254 auto sourceMultiplier = readSourceFinal(config_.sourceMultiplierAttribute, 1.0);
255 const bool sourceMultiplierOk = sourceMultiplier.ok();
256 if (!sourceMultiplierOk) {
257 const auto status = sourceMultiplier.status();
259 }
260 multiplier = sourceMultiplier.value();
261 }
262 if (!std::isfinite(multiplier) || multiplier < 0.0)
265 "RPG source multiplier must be finite and non-negative", "source_multiplier"));
266
267 auto scaled = context.setMagnitude(context.magnitude() * multiplier);
268 const bool scaledOk = scaled.ok();
269 if (!scaledOk) {
270 const auto status = scaled.status();
272 }
273
274 bool critical = boolValue(contextValue(context, "critical")).value_or(false);
275 if (!critical) {
276 const auto roll = numberValue(contextValue(context, "critical_roll"));
277 auto chance = readSourceFinal(config_.criticalChanceAttribute, 0.0);
278 const bool chanceOk = chance.ok();
279 if (!chanceOk) {
280 const auto status = chance.status();
282 }
283 if (roll && std::isfinite(*roll)) {
284 const double probability = std::clamp(chance.value(), 0.0, 1.0);
285 critical = *roll >= 0.0 && *roll < probability;
286 }
287 }
288
289 double criticalMultiplier = 1.0;
290 if (critical) {
291 if (const auto value = numberValue(contextValue(context, "critical_multiplier"))) {
292 criticalMultiplier = *value;
293 } else {
294 auto configured = readSourceFinal(config_.criticalMultiplierAttribute, 2.0);
295 const bool configuredOk = configured.ok();
296 if (!configuredOk) {
297 const auto status = configured.status();
299 }
300 criticalMultiplier = configured.value();
301 }
302 if (!std::isfinite(criticalMultiplier) || criticalMultiplier < 1.0)
304 eve::DiagnosticCode::InvalidArgument, "RPG critical multiplier must be finite and at least one",
305 "critical_multiplier"));
306 auto critScaled = context.setMagnitude(context.magnitude() * criticalMultiplier);
307 const bool critScaledOk = critScaled.ok();
308 if (!critScaledOk) {
309 const auto status = critScaled.status();
311 }
312 }
313 context.setCritical(critical);
314 context.setStageDetail("source_multiplier", multiplier);
315 context.setStageDetail("critical", critical);
316 if (critical) context.setStageDetail("critical_multiplier", criticalMultiplier);
318}
319
321 if (!isDamage(context)) return eve::Result<void>::success();
322
323 std::string element = stringValue(contextValue(context, "element")).value_or("damage");
324 const std::string resistanceAttribute = config_.resistancePrefix + element;
325 auto resistance = readFinal(resistanceAttribute, 0.0);
326 const bool resistanceOk = resistance.ok();
327 if (!resistanceOk) {
328 const auto status = resistance.status();
330 }
331 const double factor = 1.0 - std::clamp(resistance.value(), 0.0, 1.0);
332 const double before = context.magnitude();
333 auto changed = context.setMagnitude(before * factor);
334 const bool changedOk = changed.ok();
335 if (!changedOk) {
336 const auto status = changed.status();
338 }
339 auto recorded = context.addResisted(before - context.magnitude());
340 const bool recordedOk = recorded.ok();
341 if (!recordedOk) {
342 const auto status = recorded.status();
344 }
345 context.setStageDetail("element", element);
346 context.setStageDetail("resistance", resistance.value());
348}
349
351 if (!isDamage(context)) return eve::Result<void>::success();
352
353 auto armor = readFinal(config_.armorAttribute, 0.0);
354 const bool armorOk = armor.ok();
355 if (!armorOk) {
356 const auto status = armor.status();
358 }
359 const double penetration = std::max(0.0, numberValue(contextValue(context, "penetration")).value_or(0.0));
360 const double effectiveArmor = std::max(0.0, armor.value() - penetration);
361 const double armorFactor = 100.0 / (100.0 + effectiveArmor);
362 const double beforeArmor = context.magnitude();
363 auto armorChanged = context.setMagnitude(beforeArmor * armorFactor);
364 const bool armorChangedOk = armorChanged.ok();
365 if (!armorChangedOk) {
366 const auto status = armorChanged.status();
368 }
369 auto armorLoss = context.addResisted(beforeArmor - context.magnitude());
370 const bool armorLossOk = armorLoss.ok();
371 if (!armorLossOk) {
372 const auto status = armorLoss.status();
374 }
375
376 auto shield = readBase(config_.shieldAttribute);
377 double shieldValue = 0.0;
378 if (shield.ok()) {
379 shieldValue = shield.value();
380 } else if (shield.code() != eve::StatusCode::NotFound) {
381 const auto status = shield.status();
383 }
384 const double absorbed = std::min(shieldValue, context.magnitude());
385 auto shieldChanged = context.setMagnitude(context.magnitude() - absorbed);
386 const bool shieldChangedOk = shieldChanged.ok();
387 if (!shieldChangedOk) {
388 const auto status = shieldChanged.status();
390 }
391 auto absorbedRecorded = context.addAbsorbed(absorbed);
392 const bool absorbedRecordedOk = absorbedRecorded.ok();
393 if (!absorbedRecordedOk) {
394 const auto status = absorbedRecorded.status();
396 }
397 context.setStageDetail("armor", armor.value());
398 context.setStageDetail("penetration", penetration);
399 context.setStageDetail("shield", shieldValue);
400 context.setStageDetail("absorbed", absorbed);
402}
403
405 auto health = readBase(config_.healthAttribute);
406 const bool healthOk = health.ok();
407 if (!healthOk) {
408 const auto status = health.status();
410 }
411 auto maximum = readBase(config_.maxHealthAttribute);
412 const bool maximumOk = maximum.ok();
413 if (!maximumOk) {
414 const auto status = maximum.status();
416 }
417 const double bound = isDamage(context) ? health.value() : maximum.value() - health.value();
418 auto configured = context.setClampMax(std::max(0.0, bound));
419 const bool configuredOk = configured.ok();
420 if (!configuredOk) {
421 const auto status = configured.status();
423 }
424 context.setStageDetail("maximum", bound);
426}
427
430 auto health = readBase(config_.healthAttribute);
431 const bool healthOk = health.ok();
432 if (!healthOk) {
433 const auto status = health.status();
435 }
436 auto maximum = readBase(config_.maxHealthAttribute);
437 const bool maximumOk = maximum.ok();
438 if (!maximumOk) {
439 const auto status = maximum.status();
441 }
442
443 auto candidate = target_.attributes()->values;
444 const double nextHealth =
445 isDamage(context) ? health.value() - context.magnitude() : health.value() + context.magnitude();
446 if (!std::isfinite(nextHealth) || nextHealth < 0.0 || nextHealth > maximum.value())
449 "RPG settlement candidate health is outside its validated bounds", "health"));
450 candidate.setBase(config_.healthAttribute, nextHealth);
451
452 if (isDamage(context) && !config_.shieldAttribute.empty() && context.absorbed() > 0.0) {
453 auto shield = readBase(config_.shieldAttribute);
454 const bool shieldOk = shield.ok();
455 if (!shieldOk) {
456 const auto status = shield.status();
457 if (status.code() != eve::StatusCode::NotFound)
459 } else {
460 if (context.absorbed() > shield.value())
463 "RPG shield absorption exceeds the authoritative shield balance", "shield"));
464 candidate.setBase(config_.shieldAttribute, shield.value() - context.absorbed());
465 }
466 }
467
468 struct Pending {
469 RPGActor* actor = nullptr;
472 bool published = false;
473 };
474 auto pending =
475 std::make_shared<Pending>(Pending{&target_, target_.attributes()->values, std::move(candidate), false});
478 pending->actor->attributes()->values = std::move(pending->candidate);
479 pending->published = true;
480 return success(eve::StatusCode::Applied);
481 },
482 [pending]() {
483 if (pending->published) pending->actor->attributes()->values = pending->before;
484 }));
485}
486
489 std::vector<settlement::SettlementRequest> transitions;
490 if (!isDamage(context))
491 return eve::Result<std::vector<settlement::SettlementRequest>>::success(std::move(transitions));
492
493 const auto append = [&](std::string key) {
494 settlement::SettlementRequest transition = context.request();
495 transition.kind = "trigger";
496 transition.resource.clear();
497 transition.magnitude = 0.0;
498 transition.decisions.clear();
499 transition.trigger = std::move(key);
500 transition.chain = {};
501 transition.tags.push_back("trigger:" + transition.trigger);
502 transitions.push_back(std::move(transition));
503 };
504
505 auto shield = readBase(config_.shieldAttribute);
506 if (shield.ok() && shield.value() > 0.0 && result.absorbed >= shield.value()) append("shield_break");
507 if (!shield.ok() && shield.code() != eve::StatusCode::NotFound)
508 return eve::Result<std::vector<settlement::SettlementRequest>>::failure(shield.status());
509
510 auto health = readBase(config_.healthAttribute);
511 if (!health) return eve::Result<std::vector<settlement::SettlementRequest>>::failure(health.status());
512 const bool died = config_.emitLifeTransitions && health.value() > 0.0 && result.applied >= health.value();
513 if (died) {
514 append("death");
515 if (context.request().source.isValid() && context.request().source != context.request().target) append("kill");
516 }
517 return eve::Result<std::vector<settlement::SettlementRequest>>::success(std::move(transitions));
518}
519
520} // namespace eve::rpg
LogicalId target
double value
RPG's thin compatibility facade over the attributes module.
std::vector< QuestEvent > pending
std::map< std::string, Var > values
std::string message
DiagnosticCode code
float maximum[3]
const GltfImportRequest & request
std::uint32_t key
wgpu::PopErrorScopeStatus status
TokenKind kind
std::string name
const std::string * tag
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
double number
bool found
std::string resource
bool boolean
std::string string
std::string element
SimulationTick tick
Json object
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
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
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
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
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
Generic owning collection of canonical attributes and modifiers.
static const EffectDefinition * find(const std::string &id)
Find a registered effect definition by id.
Definition Effect.cpp:30
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
Settles damage/healing against one RPG actor's AttributeSet.
eve::Result< void > sourceModifiers(settlement::SettlementContext &context) override
Apply source-side modifiers such as critical or elemental power.
eve::Result< settlement::PreparedApply > prepareApply(const settlement::SettlementContext &context) override
Build an atomic candidate mutation without publishing it.
eve::Result< void > clamp(settlement::SettlementContext &context) override
Configure policy-specific amount bounds for the generic clamp.
eve::Result< void > armorShield(settlement::SettlementContext &context) override
Apply armor and shield policy, recording absorbed amount.
eve::Result< std::vector< settlement::SettlementRequest > > prepareTrigger(const settlement::SettlementContext &context, const settlement::SettlementResult &result) override
Produce owning derived requests without mutating domain state.
eve::Result< void > validate(settlement::SettlementContext &context) override
Validate request and target-owned state without mutating it.
RPGSettlementAdapter(RPGActor &target, SubjectRef targetRef)
Bind a target actor with default RPG attribute names.
eve::Result< void > targetMitigation(settlement::SettlementContext &context) override
Apply target mitigation such as resistance or penetration rules.
Prepared domain mutation used to make settlement application atomic.
Definition Settlement.h:281
Mutable calculation frame visible to policy and custom stage code.
Definition Settlement.h:338
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
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.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
RPG AttributeSet policy adapter for the common settlement pipeline.
EVENGINE_API_PLATFORM public API.
Definition Effect.h:35
Attribute names used by the RPG policy.
std::string maxHealthAttribute
Final attribute supplying the resource maximum.
bool emitLifeTransitions
Emit death/kill transitions when this resource crosses zero.
std::string healthAttribute
Vitals resource holding authoritative current health.
周期性状态触发的 tick 事件。周期效果(period > 0)不会自动修改属性, 而是每个周期产生一个 tick 事件交给上层(脚本或 C++ 结算系统)处理—— 这样伤害/治疗类周期效果可以完整地...
Definition StatusTypes.h:69
Domain-neutral input to one settlement operation.
Definition Settlement.h:107
std::string resource
Optional domain resource/channel key, for example hp, mana, armor, or fire.
Definition Settlement.h:113
std::string trigger
Stable re-entry key for a derived request; empty only for a root request.
Definition Settlement.h:119
std::vector< std::string > tags
Definition Settlement.h:116
std::vector< SettlementRandomDecision > decisions
Definition Settlement.h:117
SettlementChainContext chain
Definition Settlement.h:120
Complete observable outcome of one successful settlement.
Definition Settlement.h:163