载入中...
搜索中...
未找到
RPGSaveSession.cpp
浏览该文件的文档.
2#include "rpg/Party.h"
3
4#include "common/Snapshot.h"
5#include "common/Value.h"
7#include "rpg/GameState.h"
8#include "rpg/Quest.h"
9#include "rpg/RPGActor.h"
10#include "rpg/Tracker.h"
11
12#include <algorithm>
13#include <optional>
14#include <cstdint>
15#include <unordered_map>
16
17namespace eve::rpg {
18
19namespace {
20
21bool validContentVersion(std::string_view contentVersion) {
22 return !contentVersion.empty() && contentVersion.size() <= 256 &&
23 std::none_of(contentVersion.begin(), contentVersion.end(), [](unsigned char value) {
24 return value < 0x20u || value == 0x7fu;
25 });
26}
27
28eve::LogicalId saveSchema() {
29 const auto schema = eve::LogicalId::parse("rpg:save-session");
30 return schema ? *schema : eve::LogicalId{};
31}
32
34eve::SnapshotHashProvider saveIntegrityProvider() {
35 return [](std::string_view input) -> eve::Result<eve::ContentId> {
36 std::uint64_t left = 14695981039346656037ull;
37 std::uint64_t right = 1099511628211ull;
38 for (const unsigned char byte : input) {
39 left ^= byte;
40 left *= 1099511628211ull;
41 right ^= static_cast<std::uint64_t>(byte) + 0x9e3779b97f4a7c15ull;
42 right *= 14029467366897019727ull;
43 }
45 for (int index = 0; index < 8; ++index) {
46 bytes[static_cast<std::size_t>(index)] = static_cast<std::uint8_t>(left >> (56 - index * 8));
47 bytes[static_cast<std::size_t>(index + 8)] = static_cast<std::uint8_t>(right >> (56 - index * 8));
48 }
50 };
51}
52
53std::size_t renameStringValue(eve::Value *value, std::string_view oldId, std::string_view newId) {
54 if (!value || !value->isString() || value->asString() != oldId) return 0;
55 *value = eve::Value(std::string(newId));
56 return 1;
57}
58
59template <typename Visitor>
60std::size_t visitArray(eve::Value *value, Visitor visitor) {
61 if (!value || !value->isArray()) return 0;
62 std::size_t changed = 0;
63 for (std::size_t index = 0; index < value->arraySize(); ++index) changed += visitor(value->at(index));
64 return changed;
65}
66
67std::size_t applyIdRename(eve::Value &payload, RPGSaveIdDomain domain, std::string_view oldId,
68 std::string_view newId) {
69 eve::Value *actor = payload.find("actor");
70 eve::Value *tracker = payload.find("questTracker");
71 eve::Value *inventory = payload.find("inventory");
72 switch (domain) {
74 std::size_t changed = 0;
75 eve::Value *bag = inventory ? inventory->find("bag") : nullptr;
76 changed += visitArray(bag ? bag->find("slots") : nullptr, [&](eve::Value &stack) {
77 return renameStringValue(stack.find("itemId"), oldId, newId);
78 });
79 eve::Value *equipment = inventory ? inventory->find("equipment") : nullptr;
80 changed += visitArray(equipment ? equipment->find("slots") : nullptr, [&](eve::Value &slot) {
81 eve::Value *stack = slot.find("stack");
82 return renameStringValue(stack ? stack->find("itemId") : nullptr, oldId, newId);
83 });
84 return changed;
85 }
87 return visitArray(tracker ? tracker->find("entries") : nullptr, [&](eve::Value &entry) {
88 return renameStringValue(entry.find("id"), oldId, newId);
89 });
91 return visitArray(tracker ? tracker->find("entries") : nullptr, [&](eve::Value &entry) {
92 return visitArray(entry.find("objectives"), [&](eve::Value &objective) {
93 return renameStringValue(objective.find("id"), oldId, newId);
94 });
95 });
97 return visitArray(actor ? actor->find("learnedSkills") : nullptr, [&](eve::Value &skill) {
98 return renameStringValue(&skill, oldId, newId);
99 });
101 eve::Value *classValue = actor ? actor->find("class") : nullptr;
102 return renameStringValue(classValue ? classValue->find("id") : nullptr, oldId, newId);
103 }
105 return visitArray(actor ? actor->find("traits") : nullptr, [&](eve::Value &trait) {
106 return renameStringValue(trait.find("traitId"), oldId, newId);
107 });
109 return visitArray(actor ? actor->find("traits") : nullptr, [&](eve::Value &trait) {
110 return renameStringValue(trait.find("source"), oldId, newId);
111 });
113 eve::Value *equipment = inventory ? inventory->find("equipment") : nullptr;
114 return visitArray(equipment ? equipment->find("slots") : nullptr, [&](eve::Value &slot) {
115 return renameStringValue(slot.find("name"), oldId, newId);
116 });
117 }
118 }
119 return 0;
120}
121
122eve::Result<void> applyQuestAdditions(eve::Value &payload, const std::set<std::string> &questIds) {
123 eve::Value *tracker = payload.find("questTracker");
124 eve::Value *entries = tracker ? tracker->find("entries") : nullptr;
125 if (!tracker || !tracker->isObject() || !entries || !entries->isArray())
127 eve::DiagnosticCode::ParseError, "quest-addition migration requires a tracker entries array",
128 "payload.questTracker.entries"));
129
130 Tracker baseline;
131 auto baselineJson = baseline.snapshotJson();
132 if (!baselineJson.ok()) return eve::Result<void>::failure(baselineJson.status());
133 auto parsedBaseline = eve::Value::fromJson(baselineJson.value());
134 if (!parsedBaseline.ok()) return eve::Result<void>::failure(parsedBaseline.status());
135 eve::Value baselineRoot = std::move(parsedBaseline).takeValue();
136 eve::Value *baselineEntries = baselineRoot.find("entries");
137 if (!baselineEntries || !baselineEntries->isArray())
139 "current tracker did not produce an entries array",
140 "questTracker.entries"));
141
142 std::unordered_map<std::string, eve::Value> oldEntries;
143 for (std::size_t index = 0; index < entries->arraySize(); ++index) {
144 const eve::Value &entry = entries->at(index);
145 const eve::Value *id = entry.isObject() ? entry.find("id") : nullptr;
146 if (!id || !id->isString())
148 eve::DiagnosticCode::ParseError, "quest-addition migration found an invalid old tracker entry",
149 "payload.questTracker.entries[" + std::to_string(index) + "].id"));
150 if (!oldEntries.emplace(id->asString(), entry).second)
152 eve::DiagnosticCode::Conflict, "quest-addition migration found a duplicate old quest id",
153 "payload.questTracker.entries[" + std::to_string(index) + "].id"));
154 }
155 for (const auto &questId : questIds) {
156 if (oldEntries.contains(questId))
158 eve::DiagnosticCode::Conflict, "quest-addition migration target already exists in the old tracker",
159 "payload.questTracker.entries." + questId));
160 }
161
162 eve::Value::Array rebuilt;
163 rebuilt.reserve(baselineEntries->arraySize());
164 std::set<std::string> appliedAdditions;
165 for (std::size_t index = 0; index < baselineEntries->arraySize(); ++index) {
166 const eve::Value &initial = baselineEntries->at(index);
167 const eve::Value *id = initial.find("id");
168 if (!id || !id->isString())
170 "current tracker produced an invalid quest id",
171 "questTracker.entries"));
172 auto old = oldEntries.find(id->asString());
173 if (old != oldEntries.end()) {
174 rebuilt.push_back(std::move(old->second));
175 oldEntries.erase(old);
176 } else if (questIds.contains(id->asString())) {
177 rebuilt.push_back(initial);
178 appliedAdditions.emplace(id->asString());
179 } else {
181 eve::DiagnosticCode::Conflict, "old tracker is missing a quest without an additive migration",
182 "payload.questTracker.entries." + id->asString()));
183 }
184 }
185 if (!oldEntries.empty())
187 eve::DiagnosticCode::Conflict, "old tracker contains a quest absent from the current registry",
188 "payload.questTracker.entries." + oldEntries.begin()->first));
189 if (appliedAdditions != questIds)
191 eve::DiagnosticCode::NotFound, "quest-addition migration target is absent from the current registry",
192 "questTracker.entries"));
193 entries->operator=(eve::Value(std::move(rebuilt)));
195}
196
197} // namespace
198
199void RPGSaveSession::bind(GameState &gameState, Tracker &tracker, RPGActor &actor, inventory::Bag &bag,
200 inventory::EquipmentSet &equipment) noexcept {
201 gameState_ = &gameState;
202 tracker_ = &tracker;
203 actor_ = &actor;
204 party_ = nullptr;
205 bag_ = &bag;
206 equipment_ = &equipment;
207}
208
209void RPGSaveSession::bindParty(GameState &gameState, Tracker &tracker, Party &party,
210 inventory::Bag &bag, inventory::EquipmentSet &equipment) noexcept {
211 gameState_ = &gameState;
212 tracker_ = &tracker;
213 actor_ = nullptr;
214 party_ = &party;
215 bag_ = &bag;
216 equipment_ = &equipment;
217}
218
219eve::Result<void> RPGSaveSession::setContentVersion(std::string_view contentVersion) {
220 if (!validContentVersion(contentVersion))
223 "RPG save content version must be a non-empty identifier without controls", "contentVersion"));
224 if (!contentVersion_.empty() && contentVersion_ != contentVersion &&
225 (!compatibleContentVersions_.empty() || !idRenameMigrations_.empty() ||
226 !questAdditionMigrations_.empty() || !singleActorPartyMigrations_.empty()))
228 eve::DiagnosticCode::Conflict, "RPG content version cannot change after migration routes are registered",
229 "contentVersion"));
230 contentVersion_ = std::string(contentVersion);
231 compatibleContentVersions_.erase(contentVersion_);
233}
234
236 if (!validContentVersion(contentVersion))
239 "compatible RPG content version must be a non-empty identifier without controls", "contentVersion"));
240 if (contentVersion == contentVersion_)
242 eve::DiagnosticCode::Conflict, "current RPG content version must not be registered as an older version",
243 "contentVersion"));
244 const bool hasMigration = std::any_of(idRenameMigrations_.begin(), idRenameMigrations_.end(),
245 [&](const auto &entry) {
246 return entry.first.fromContentVersion == contentVersion;
247 }) ||
248 questAdditionMigrations_.contains(std::string(contentVersion)) ||
249 singleActorPartyMigrations_.contains(std::string(contentVersion));
250 if (hasMigration)
252 eve::DiagnosticCode::Conflict, "RPG content version already has an ID migration route", "contentVersion"));
253 if (!compatibleContentVersions_.emplace(contentVersion).second)
255 "compatible RPG content version is already registered",
256 "contentVersion"));
258}
259
260eve::Result<void> RPGSaveSession::addIdRenameMigration(std::string_view fromContentVersion,
261 RPGSaveIdDomain domain, std::string_view oldId,
262 std::string_view newId) {
263 if (contentVersion_.empty())
266 "current RPG content version must be configured before migration rules", "contentVersion"));
267 if (!validContentVersion(fromContentVersion) || !validContentVersion(oldId) ||
268 !validContentVersion(newId) || oldId == newId)
271 "RPG ID migration requires distinct valid source and destination identifiers", "migration"));
272 if (fromContentVersion == contentVersion_)
274 eve::DiagnosticCode::Conflict, "RPG ID migration source must differ from the current content version",
275 "fromContentVersion"));
276 if (compatibleContentVersions_.contains(std::string(fromContentVersion)))
278 eve::DiagnosticCode::Conflict, "RPG content version is already admitted as directly compatible",
279 "fromContentVersion"));
280 MigrationKey key{std::string(fromContentVersion), domain, std::string(oldId)};
281 if (!idRenameMigrations_.emplace(std::move(key), std::string(newId)).second)
283 eve::DiagnosticCode::AlreadyExists, "RPG ID migration source is already registered", "migration"));
285}
286
288 std::string_view questId) {
289 if (contentVersion_.empty())
292 "current RPG content version must be configured before migration rules", "contentVersion"));
293 if (!validContentVersion(fromContentVersion) || !validContentVersion(questId))
296 "quest-addition migration requires valid source and quest identifiers", "migration"));
297 if (fromContentVersion == contentVersion_)
300 "quest-addition migration source must differ from the current content version", "fromContentVersion"));
301 if (compatibleContentVersions_.contains(std::string(fromContentVersion)))
303 eve::DiagnosticCode::Conflict, "RPG content version is already admitted as directly compatible",
304 "fromContentVersion"));
305 if (!QuestRegistry::find(std::string(questId)))
307 eve::DiagnosticCode::NotFound, "quest-addition migration target is not registered", "questId"));
308 auto &quests = questAdditionMigrations_[std::string(fromContentVersion)];
309 if (!quests.emplace(questId).second)
311 eve::DiagnosticCode::AlreadyExists, "quest-addition migration is already registered", "migration"));
313}
314
316 std::string_view fromContentVersion) {
317 if (contentVersion_.empty())
320 "current RPG content version must be configured before migration rules", "contentVersion"));
321 if (!validContentVersion(fromContentVersion) || fromContentVersion == contentVersion_)
324 "single-actor party migration requires a distinct valid source version", "fromContentVersion"));
325 if (compatibleContentVersions_.contains(std::string(fromContentVersion)))
327 eve::DiagnosticCode::Conflict, "RPG content version is already admitted as directly compatible",
328 "fromContentVersion"));
329 if (!singleActorPartyMigrations_.emplace(fromContentVersion).second)
331 "single-actor party migration is already registered",
332 "fromContentVersion"));
334}
335
337 if (!gameState_ || !tracker_ || (!actor_ && !party_) || !bag_ || !equipment_)
339 eve::DiagnosticCode::PreconditionViolation, "RPG save session requires bound participants", {}));
340 if (contentVersion_.empty())
342 eve::DiagnosticCode::PreconditionViolation, "RPG save session requires a content version", {}));
343 auto gameStateJson = gameState_->snapshotJson();
344 if (!gameStateJson.ok()) return eve::Result<std::string>::failure(gameStateJson.status());
345 auto trackerJson = tracker_->snapshotJson();
346 if (!trackerJson.ok()) return eve::Result<std::string>::failure(trackerJson.status());
347 auto actorJson = actor_ ? actor_->checkpointJson() : party_->checkpointJson();
348 if (!actorJson.ok()) return eve::Result<std::string>::failure(actorJson.status());
349 inventory::InventorySaveSession inventorySession;
350 inventorySession.bind(*bag_, *equipment_);
351 auto inventoryJson = inventorySession.snapshotJson();
352 if (!inventoryJson.ok()) return eve::Result<std::string>::failure(inventoryJson.status());
353 auto gameStateValue = eve::Value::fromJson(gameStateJson.value());
354 if (!gameStateValue.ok()) return eve::Result<std::string>::failure(gameStateValue.status());
355 auto trackerValue = eve::Value::fromJson(trackerJson.value());
356 if (!trackerValue.ok()) return eve::Result<std::string>::failure(trackerValue.status());
357 auto actorValue = eve::Value::fromJson(actorJson.value());
358 if (!actorValue.ok()) return eve::Result<std::string>::failure(actorValue.status());
359 auto inventoryValue = eve::Value::fromJson(inventoryJson.value());
360 if (!inventoryValue.ok()) return eve::Result<std::string>::failure(inventoryValue.status());
362 payload.emplace(actor_ ? "actor" : "party", std::move(actorValue).takeValue());
363 payload.emplace("contentVersion", eve::Value(contentVersion_));
364 payload.emplace("gameState", std::move(gameStateValue).takeValue());
365 payload.emplace("inventory", std::move(inventoryValue).takeValue());
366 payload.emplace("questTracker", std::move(trackerValue).takeValue());
367 const eve::SchemaVersion schemaVersion(actor_ ? 1 : 2);
368 auto envelope = eve::makeSnapshotEnvelope("rpg.save-session", saveSchema(), schemaVersion,
370 eve::Value(std::move(payload)), saveIntegrityProvider());
371 if (!envelope.ok()) return eve::Result<std::string>::failure(envelope.status());
372 return eve::serializeSnapshotEnvelope(envelope.value());
373}
374
376 return restoreSnapshotJsonImpl(json, false);
377}
378
380 return restoreSnapshotJsonImpl(json, true);
381}
382
383eve::Result<void> RPGSaveSession::restoreSnapshotJsonImpl(std::string_view json, bool publish) {
384 if (!gameState_ || !tracker_ || (!actor_ && !party_) || !bag_ || !equipment_)
386 "RPG save session requires bound participants", {}));
387 if (contentVersion_.empty())
389 "RPG save session requires a content version", {}));
390 auto parsed = eve::parseSnapshotEnvelope(json, saveIntegrityProvider());
391 if (!parsed.ok()) return eve::Result<void>::failure(parsed.status());
392 const eve::SnapshotEnvelope &envelope = parsed.value();
393 if (envelope.type != "rpg.save-session")
395 "snapshot does not belong to RPGSaveSession", "type"));
396 if (envelope.schema != saveSchema())
398 eve::DiagnosticCode::InvalidArgument, "snapshot does not belong to RPGSaveSession", "schema"));
399 eve::Value root = envelope.payload;
400 if (!root.isObject())
402 eve::DiagnosticCode::ParseError, "RPG save session payload must be an object", "payload"));
403 eve::Value *contentVersion = root.find("contentVersion");
404 if (!contentVersion || !contentVersion->isString())
407 "RPG save-session content version is missing or invalid", "payload.contentVersion"));
408 const std::string sourceContentVersion = contentVersion->asString();
409 const bool migrateSingleActor = party_ && envelope.schemaVersion == eve::SchemaVersion(1) &&
410 singleActorPartyMigrations_.contains(sourceContentVersion);
411 const eve::SchemaVersion expectedVersion(actor_ ? 1 : 2);
412 if (envelope.schemaVersion != expectedVersion && !migrateSingleActor)
414 eve::DiagnosticCode::UnknownVersion, "unsupported RPG save-session snapshot version", "schemaVersion"));
415 if (migrateSingleActor) {
416 const eve::Value *oldActor = root.find("actor");
417 if (!oldActor || !oldActor->isObject())
419 eve::DiagnosticCode::ParseError, "single-actor migration requires an actor payload", "payload.actor"));
420 auto baselineJson = party_->checkpointJson();
421 if (!baselineJson.ok()) return eve::Result<void>::failure(baselineJson.status());
422 auto baseline = eve::Value::fromJson(baselineJson.value());
423 if (!baseline.ok()) return eve::Result<void>::failure(baseline.status());
424 eve::Value migratedParty = std::move(baseline).takeValue();
425 eve::Value *members = migratedParty.find("members");
426 if (!members || !members->isArray() || members->arraySize() == 0)
429 "single-actor migration requires a non-empty live party", "party"));
430 members->at(0).set("actor", *oldActor);
431 root.set("party", std::move(migratedParty));
432 }
433 if (sourceContentVersion != contentVersion_ &&
434 !compatibleContentVersions_.contains(sourceContentVersion)) {
435 bool hasMigration = false;
436 for (const auto &[key, destination] : idRenameMigrations_) {
437 if (key.fromContentVersion != sourceContentVersion) continue;
438 hasMigration = true;
439 applyIdRename(root, key.domain, key.oldId, destination);
440 }
441 const auto questAdditions = questAdditionMigrations_.find(sourceContentVersion);
442 if (questAdditions != questAdditionMigrations_.end()) {
443 hasMigration = true;
444 auto added = applyQuestAdditions(root, questAdditions->second);
445 if (!added.ok()) return added;
446 }
447 if (migrateSingleActor) hasMigration = true;
448 if (!hasMigration)
450 eve::DiagnosticCode::Conflict, "RPG save-session content version has no compatible or migration route",
451 "payload.contentVersion"));
452 root.set("contentVersion", eve::Value(contentVersion_));
453 }
454 const eve::Value *gameStateValue = root.find("gameState");
455 const eve::Value *trackerValue = root.find("questTracker");
456 const eve::Value *actorValue = root.find(actor_ ? "actor" : "party");
457 const eve::Value *inventoryValue = root.find("inventory");
458 if (!gameStateValue || !gameStateValue->isObject() || !trackerValue || !trackerValue->isObject() ||
459 !actorValue || !actorValue->isObject() || !inventoryValue || !inventoryValue->isObject())
461 eve::DiagnosticCode::ParseError, "RPG save-session participants must be objects", "payload"));
462 auto gameStateJson = gameStateValue->toJson();
463 if (!gameStateJson.ok()) return eve::Result<void>::failure(gameStateJson.status());
464 auto trackerJson = trackerValue->toJson();
465 if (!trackerJson.ok()) return eve::Result<void>::failure(trackerJson.status());
466 auto actorJson = actorValue->toJson();
467 if (!actorJson.ok()) return eve::Result<void>::failure(actorJson.status());
468 auto inventoryJson = inventoryValue->toJson();
469 if (!inventoryJson.ok()) return eve::Result<void>::failure(inventoryJson.status());
470
471 GameState candidateGameState = *gameState_;
472 Tracker candidateTracker = *tracker_;
473 auto gameStateRestore = candidateGameState.restoreSnapshotJson(gameStateJson.value());
474 if (!gameStateRestore.ok()) return gameStateRestore;
475 auto trackerRestore = candidateTracker.restoreSnapshotJson(trackerJson.value());
476 if (!trackerRestore.ok()) return trackerRestore;
477 inventory::InventorySaveSession inventorySession;
478 inventorySession.bind(*bag_, *equipment_);
479 auto inventoryCandidate = inventorySession.prepareRestoreSnapshotJson(inventoryJson.value());
480 if (!inventoryCandidate.ok()) return eve::Result<void>::failure(inventoryCandidate.status());
481
482 std::optional<RPGActor::CheckpointCandidate> actorCandidate;
483 std::optional<Party::CheckpointCandidate> partyCandidate;
484 if (actor_) {
485 auto prepared = actor_->prepareCheckpointJson(actorJson.value());
486 if (!prepared.ok()) return eve::Result<void>::failure(prepared.status());
487 actorCandidate.emplace(std::move(prepared).takeValue());
488 } else {
489 auto prepared = party_->prepareCheckpointJson(actorJson.value());
490 if (!prepared.ok()) return eve::Result<void>::failure(prepared.status());
491 partyCandidate.emplace(std::move(prepared).takeValue());
492 }
493
495
496 gameState_->switches_.swap(candidateGameState.switches_);
497 gameState_->variables_.swap(candidateGameState.variables_);
498 gameState_->selfVariables_.swap(candidateGameState.selfVariables_);
499 gameState_->selfStrings_.swap(candidateGameState.selfStrings_);
500 tracker_->entries.swap(candidateTracker.entries);
501 tracker_->order.swap(candidateTracker.order);
502 tracker_->pending.swap(candidateTracker.pending);
503 tracker_->polled.swap(candidateTracker.polled);
504 if (actor_)
505 actor_->commitCheckpoint(std::move(*actorCandidate));
506 else
507 party_->commitCheckpoint(std::move(*partyCandidate));
508 inventorySession.commitPrepared(std::move(inventoryCandidate).takeValue());
510}
511
512} // namespace eve::rpg
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
std::unordered_map< std::string, QuestRuntime > entries
EvpackChunkInput input
Definition Evpack.cpp:170
全局游戏状态:开关(bool)、变量(数字)、独立变量(按作用域)。
std::uint32_t key
HexVec3 left
HexVec3 right
Versioned atomic persistence boundary for one bag and equipment set.
std::uint64_t bytes
Generation-safe ordered RPG party relationships.
std::string id
Definition PlayHost.cpp:108
任务 / 成就 / 教程的通用定义模板与进程级注册表。
Transactional composition of persistent RPG state owners.
Versioned, integrity-checked persistence envelopes.
目标追踪器运行时实例(玩家日志 / 成就 / 教程可各建一份)。
std::map< std::string, std::span< const std::uint8_t > > members
uint32_t index
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
Human-readable, scoped identifier in the form namespace:name.
Definition Identity.h:411
static std::optional< LogicalId > parse(std::string_view text)
Parses a scoped logical name.
Definition Identity.cpp:36
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
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 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::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
Id128 public API.
Definition Identity.h:112
std::array< std::uint8_t, 16 > Bytes
Definition Identity.h:114
static constexpr Id128 nil() noexcept
Returns the all-zero nil value.
Definition Identity.h:142
格子型物品容器(脚本可直接操作)。
Definition Bag.h:21
EVENGINE_API_FOUNDATION public API.
Definition Equipment.h:19
Coordinates persistent inventory owners without duplicating their live state.
eve::Result< std::string > snapshotJson() const
Capture the complete bag and equipment state.
void bind(Bag &bag, EquipmentSet &equipment) noexcept
Bind the authoritative inventory participants.
一份游戏状态(开关/变量/独立变量)。
Definition GameState.h:25
eve::Result< std::string > snapshotJson() const
Serialize this state as the canonical versioned RPG game-state JSON document.
Authoritative ordered relationship between stable party member IDs and RPG actors....
Definition Party.h:24
eve::Result< std::string > checkpointJson() const
Capture ordered member IDs and every actor safe checkpoint into one versioned document.
Definition Party.cpp:182
static const QuestDefinition * find(const std::string &id)
Find a registered quest definition by id.
Definition Quest.cpp:160
属性 / 状态 / 技能三表合一的 ECS 实体。
Definition RPGActor.h:34
eve::Result< std::string > checkpointJson() const
Capture this actor at a persistent safe checkpoint.
eve::Result< void > validateSnapshotJson(std::string_view json)
Parse and fully prepare a save without publishing participant state.
void bind(GameState &gameState, Tracker &tracker, RPGActor &actor, inventory::Bag &bag, inventory::EquipmentSet &equipment) noexcept
Bind the authoritative participants used by subsequent operations.
eve::Result< std::string > snapshotJson() const
Capture every bound participant into one deterministic versioned JSON document.
eve::Result< void > allowCompatibleContentVersion(std::string_view contentVersion)
Accept one older content contract that is data-compatible with the configured version.
eve::Result< void > addIdRenameMigration(std::string_view fromContentVersion, RPGSaveIdDomain domain, std::string_view oldId, std::string_view newId)
Register one direct old-to-current definition ID rewrite.
eve::Result< void > allowSingleActorPartyMigration(std::string_view fromContentVersion)
Register an explicit schema-v1 single-actor to schema-v2 party migration.
eve::Result< void > setContentVersion(std::string_view contentVersion)
Configure the exact game-content contract required by this save session.
eve::Result< void > addQuestAdditionMigration(std::string_view fromContentVersion, std::string_view questId)
Register an additive migration that initializes one newly introduced quest.
void bindParty(GameState &gameState, Tracker &tracker, Party &party, inventory::Bag &bag, inventory::EquipmentSet &equipment) noexcept
Bind a complete authoritative player party instead of a single compatibility actor.
eve::Result< void > restoreSnapshotJson(std::string_view json)
Validate all participants and atomically publish a save-session snapshot.
一份任务进度 + 本实例事件队列。
Definition Tracker.h:26
std::unordered_map< std::string, QuestRuntime > entries
运行时条目(QuestSystem 读写;公开以便测试与脚本查询)。
Definition Tracker.h:123
std::vector< std::string > order
Definition Tracker.h:124
eve::Result< std::string > snapshotJson() const
Serialize persistent quest progress as deterministic versioned JSON.
Definition Tracker.cpp:44
eve::Result< void > restoreSnapshotJson(std::string_view json)
Validate and atomically restore quest progress against the current registry.
Definition Tracker.cpp:73
std::vector< QuestEvent > pending
Definition Tracker.h:125
std::vector< QuestEvent > polled
Definition Tracker.h:126
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
RPGSaveIdDomain
Definition-reference field kinds supported by direct RPG save ID migrations.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Result< SnapshotEnvelope > parseSnapshotEnvelope(std::string_view json, const SnapshotHashProvider &hashProvider)
Parse and verify an envelope from canonical or compatible JSON text.
Definition Snapshot.cpp:190
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
Result< std::string > serializeSnapshotEnvelope(const SnapshotEnvelope &snapshot)
Serialize an envelope as deterministic compact JSON.
Definition Snapshot.cpp:184
Result< SnapshotEnvelope > makeSnapshotEnvelope(std::string type, LogicalId schema, SchemaVersion schemaVersion, PersistentId instanceId, Revision revision, SimulationTick tick, Value payload, const SnapshotHashProvider &hashProvider)
Construct and seal a snapshot envelope.
Definition Snapshot.cpp:103
std::function< Result< ContentId >(std::string_view canonicalInput)> SnapshotHashProvider
Injected content-digest implementation used by snapshots.
Definition Snapshot.h:36
Stable outer format shared by persistence and cross-process snapshots.
Definition Snapshot.h:46
SchemaVersion schemaVersion
Definition Snapshot.h:49
std::string type
Definition Snapshot.h:47