载入中...
搜索中...
未找到
RPGActorPersistence.cpp
浏览该文件的文档.
1#include "rpg/RPGActor.h"
2
3#include "common/Value.h"
4#include "rpg/Skill.h"
5
6#include <algorithm>
7#include <cmath>
8#include <unordered_set>
9
10namespace eve::rpg {
11
12namespace {
13
14double numericValue(const eve::Value &value) {
15 return value.isInt64() ? static_cast<double>(value.asInt()) : value.asDouble();
16}
17
18} // namespace
19
21 RPGActor *actor = const_cast<RPGActor *>(this);
22 auto statusRef = actor->statuses();
23 auto skillRef = actor->skills();
24 auto attributeRef = actor->attributes();
25 auto progressionRef = actor->progression();
26 auto vitalRef = actor->vitals();
27 auto traitRef = actor->traits();
28 auto classRef = actor->classInfo();
29 if (statusRef->container.effectCount() != 0 || skillRef->casting.active)
32 "actor checkpoint requires a safe point without active effects or casts", {}));
33
34 eve::Value::Array baseAttributes;
35 for (int index = 0; index < attributeRef->values.attributeCount(); ++index) {
36 const std::string name = attributeRef->values.attributeAt(index);
38 entry.emplace("id", eve::Value(name));
39 entry.emplace("value", eve::Value(attributeRef->values.getBase(name)));
40 baseAttributes.emplace_back(std::move(entry));
41 }
42
43 eve::Value::Object progressionValue;
44 progressionValue.emplace("level", eve::Value(progressionRef->level));
45 progressionValue.emplace("xp", eve::Value(progressionRef->xp));
46 progressionValue.emplace("xpToNext", eve::Value(progressionRef->xpToNext));
47
48 eve::Value::Object vitalsValue;
49 for (const auto &[name, value] : vitalRef->current) vitalsValue.emplace(name, eve::Value(value));
50
51 std::vector<std::string> skillIds;
52 skillIds.reserve(skillRef->known.size());
53 for (const auto &[id, runtime] : skillRef->known) {
54 (void)runtime;
55 skillIds.push_back(id);
56 }
57 std::sort(skillIds.begin(), skillIds.end());
58 eve::Value::Array learnedSkills;
59 for (const auto &id : skillIds) learnedSkills.emplace_back(id);
60
61 eve::Value::Array traitValues;
62 for (const auto &trait : traitRef->active) {
63 eve::Value::Object encoded;
64 encoded.emplace("source", eve::Value(trait.source));
65 encoded.emplace("traitId", eve::Value(trait.traitId));
66 traitValues.emplace_back(std::move(encoded));
67 }
68
69 eve::Value::Object classValue;
70 classValue.emplace("id", eve::Value(classRef->classId));
71 classValue.emplace("skillsSyncedUpTo", eve::Value(classRef->skillsSyncedUpTo));
72
74 root.emplace("baseAttributes", eve::Value(std::move(baseAttributes)));
75 root.emplace("class", eve::Value(std::move(classValue)));
76 root.emplace("learnedSkills", eve::Value(std::move(learnedSkills)));
77 root.emplace("progression", eve::Value(std::move(progressionValue)));
78 root.emplace("schema", eve::Value("eve.rpg.actor-checkpoint"));
79 root.emplace("traits", eve::Value(std::move(traitValues)));
80 root.emplace("version", eve::Value(1));
81 root.emplace("vitals", eve::Value(std::move(vitalsValue)));
82 return eve::Value(std::move(root)).toJson();
83}
84
85eve::Result<RPGActor::CheckpointCandidate> RPGActor::prepareCheckpointJson(std::string_view json) const {
86 RPGActor *actor = const_cast<RPGActor *>(this);
87 auto statusRef = actor->statuses();
88 auto skillRef = actor->skills();
89 auto attributeRef = actor->attributes();
90 auto progressionRef = actor->progression();
91 auto traitRef = actor->traits();
92 auto classRef = actor->classInfo();
93 if (statusRef->container.effectCount() != 0 || skillRef->casting.active)
96 "actor checkpoint restore requires a clean preconfigured destination", {}));
97 auto parsed = eve::Value::fromJson(json);
98 if (!parsed.ok()) return eve::Result<CheckpointCandidate>::failure(parsed.status());
99 const eve::Value &root = parsed.value();
100 if (!root.isObject())
102 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "actor checkpoint root must be an object", "$"));
103 const eve::Value *schema = root.find("schema");
104 if (!schema || !schema->isString() || schema->asString() != "eve.rpg.actor-checkpoint")
106 eve::DiagnosticCode::InvalidArgument, "snapshot does not belong to RPGActor", "$.schema"));
107 const eve::Value *version = root.find("version");
108 if (!version || !version->isInt64() || version->asInt() != 1)
110 eve::DiagnosticCode::UnknownVersion, "unsupported RPG actor checkpoint version", "$.version"));
111
112 const eve::Value *encodedClass = root.find("class");
113 const eve::Value *encodedTraits = root.find("traits");
114 const eve::Value *encodedAttributes = root.find("baseAttributes");
115 const eve::Value *encodedProgression = root.find("progression");
116 const eve::Value *encodedVitals = root.find("vitals");
117 const eve::Value *encodedSkills = root.find("learnedSkills");
118 if (!encodedClass || !encodedClass->isObject() || !encodedTraits || !encodedTraits->isArray() ||
119 !encodedAttributes || !encodedAttributes->isArray() || !encodedProgression || !encodedProgression->isObject() ||
120 !encodedVitals || !encodedVitals->isObject() || !encodedSkills || !encodedSkills->isArray())
122 eve::DiagnosticCode::ParseError, "actor checkpoint participant fields have invalid types", "$"));
123
124 const eve::Value *classId = encodedClass->find("id");
125 const eve::Value *skillsSynced = encodedClass->find("skillsSyncedUpTo");
126 if (!classId || !classId->isString() || !skillsSynced || !skillsSynced->isInt64() ||
127 classId->asString() != classRef->classId || skillsSynced->asInt() < 0)
129 eve::DiagnosticCode::Conflict, "actor checkpoint class does not match its destination", "$.class"));
130 if (encodedTraits->arraySize() != traitRef->active.size())
132 eve::DiagnosticCode::Conflict, "actor checkpoint traits do not match its destination", "$.traits"));
133 for (std::size_t index = 0; index < traitRef->active.size(); ++index) {
134 const eve::Value &encoded = encodedTraits->at(index);
135 const eve::Value *traitId = encoded.isObject() ? encoded.find("traitId") : nullptr;
136 const eve::Value *source = encoded.isObject() ? encoded.find("source") : nullptr;
137 if (!traitId || !traitId->isString() || !source || !source->isString() ||
138 traitId->asString() != traitRef->active[index].traitId || source->asString() != traitRef->active[index].source)
140 eve::DiagnosticCode::Conflict, "actor checkpoint trait identity does not match", "$.traits"));
141 }
142
143 CheckpointCandidate candidate{*attributeRef, {}, *progressionRef, {}, *classRef};
144 candidate.classInfo.skillsSyncedUpTo = static_cast<int>(skillsSynced->asInt());
145 if (encodedAttributes->arraySize() != static_cast<std::size_t>(candidate.attributes.values.attributeCount()))
147 eve::DiagnosticCode::Conflict, "actor checkpoint attribute layout does not match", "$.baseAttributes"));
148 std::unordered_set<std::string> seenAttributes;
149 for (std::size_t index = 0; index < encodedAttributes->arraySize(); ++index) {
150 const eve::Value &encoded = encodedAttributes->at(index);
151 const eve::Value *id = encoded.isObject() ? encoded.find("id") : nullptr;
152 const eve::Value *value = encoded.isObject() ? encoded.find("value") : nullptr;
153 if (!id || !id->isString() || !value || !value->isNumeric() || !std::isfinite(numericValue(*value)))
155 eve::DiagnosticCode::ParseError, "actor base attribute is invalid", "$.baseAttributes"));
156 if (!seenAttributes.emplace(id->asString()).second || !candidate.attributes.values.has(id->asString()))
158 eve::DiagnosticCode::Conflict, "actor base attribute layout does not match", "$.baseAttributes"));
159 candidate.attributes.values.setBase(id->asString(), numericValue(*value));
160 }
161
162 const eve::Value *level = encodedProgression->find("level");
163 const eve::Value *xp = encodedProgression->find("xp");
164 const eve::Value *xpToNext = encodedProgression->find("xpToNext");
165 if (!level || !level->isInt64() || level->asInt() < 1 || !xp || !xp->isNumeric() ||
166 !xpToNext || !xpToNext->isNumeric() || !std::isfinite(numericValue(*xp)) ||
167 !std::isfinite(numericValue(*xpToNext)) || numericValue(*xp) < 0.0 ||
168 numericValue(*xpToNext) <= numericValue(*xp))
170 eve::DiagnosticCode::InvalidArgument, "actor checkpoint progression is invalid", "$.progression"));
171 candidate.progression = {static_cast<int>(level->asInt()), numericValue(*xp), numericValue(*xpToNext)};
172
173 for (const auto &name : encodedVitals->keys()) {
174 const eve::Value *value = encodedVitals->find(name);
175 if (!value || !value->isNumeric() || !std::isfinite(numericValue(*value)) || numericValue(*value) < 0.0 ||
176 numericValue(*value) > candidate.attributes.values.getFinal(name))
179 "actor checkpoint vital is outside its attribute bounds", "$.vitals." + name));
180 candidate.vitals.current.emplace(name, numericValue(*value));
181 }
182
183 std::unordered_set<std::string> seenSkills;
184 for (std::size_t index = 0; index < encodedSkills->arraySize(); ++index) {
185 const eve::Value &id = encodedSkills->at(index);
186 if (!id.isString() || id.asString().empty() || !seenSkills.emplace(id.asString()).second ||
187 !SkillRegistry::find(id.asString()))
189 eve::DiagnosticCode::NotFound, "actor checkpoint skill is missing or invalid", "$.learnedSkills"));
190 candidate.skills.known.emplace(id.asString(), SkillRuntime{});
191 }
192 candidate.skills.casting = CastingState{};
193 return eve::Result<CheckpointCandidate>::success(std::move(candidate));
194}
195
196void RPGActor::commitCheckpoint(CheckpointCandidate candidate) noexcept {
197 *attributes() = std::move(candidate.attributes);
198 *skills() = std::move(candidate.skills);
199 *progression() = candidate.progression;
200 *vitals() = std::move(candidate.vitals);
201 *classInfo() = std::move(candidate.classInfo);
202}
203
205 auto prepared = prepareCheckpointJson(json);
206 if (!prepared.ok()) return eve::Result<void>::failure(prepared.status());
207 commitCheckpoint(std::move(prepared).takeValue());
209}
210
211} // namespace eve::rpg
double value
int root
Definition AnimSmr.cpp:119
std::string name
int level
std::string id
Definition PlayHost.cpp:108
std::uint32_t classId
uint32_t index
const UnitySourceAsset & source
Owning, renderer-independent dynamic values.
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
bool isInt64() const noexcept
Return true when this value is an Int64.
Definition Value.h:91
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
bool isNumeric() const noexcept
Return whether this value is an integer or finite number.
Definition Value.h:159
bool isArray() const noexcept
Return true when this value is an array.
Definition Value.h:97
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
std::int64_t asInt() const
Return the Int64 payload; the caller must have checked the kind.
Definition Value.cpp:69
std::vector< Value > Array
Definition Value.h:33
std::size_t arraySize() const
Return the number of array elements.
Definition Value.cpp:100
bool isObject() const noexcept
Return true when this value is an object.
Definition Value.h:99
Value & at(std::size_t index)
Return a bounds-checked array element.
Definition Value.cpp:106
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
eve::Result< std::string > checkpointJson() const
Capture this actor at a persistent safe checkpoint.
eve::Result< void > restoreCheckpointJson(std::string_view json)
Validate and atomically restore a safe checkpoint into this preconfigured actor.
static const SkillDefinition * find(const std::string &id)
Find a registered skill definition by id.
Definition Skill.cpp:90
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。