载入中...
搜索中...
未找到
InventorySaveSession.cpp
浏览该文件的文档.
2
3#include "common/Value.h"
4#include "inventory/Bag.h"
7#include "inventory/Item.h"
8
9#include <algorithm>
10#include <cmath>
11#include <memory>
12#include <unordered_set>
13
14namespace eve::inventory {
15
16namespace {
17
18double number(const eve::Value &value) {
19 return value.isInt64() ? static_cast<double>(value.asInt()) : value.asDouble();
20}
21
22eve::Value encodeStrings(const std::vector<std::string> &values) {
23 eve::Value::Array encoded;
24 encoded.reserve(values.size());
25 for (const auto &value : values) encoded.emplace_back(value);
26 return eve::Value(std::move(encoded));
27}
28
29eve::Value encodeMap(const std::unordered_map<std::string, std::string> &values) {
30 eve::Value::Object encoded;
31 for (const auto &[key, value] : values) encoded.emplace(key, eve::Value(value));
32 return eve::Value(std::move(encoded));
33}
34
35eve::Value encodeStack(const ItemStack &stack) {
36 eve::Value::Object encoded;
37 encoded.emplace("durability", eve::Value(stack.durability));
38 encoded.emplace("instanceId", eve::Value(stack.instanceId));
39 encoded.emplace("itemId", eve::Value(stack.itemId));
40 encoded.emplace("props", encodeMap(stack.props));
41 encoded.emplace("quantity", eve::Value(stack.quantity));
42 encoded.emplace("tags", encodeStrings(stack.tags));
43 return eve::Value(std::move(encoded));
44}
45
46eve::Result<std::vector<std::string>> decodeStrings(const eve::Value *encoded, const std::string &path) {
47 if (!encoded || !encoded->isArray())
49 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "expected an array of strings", path));
50 std::vector<std::string> result;
51 std::unordered_set<std::string> seen;
52 result.reserve(encoded->arraySize());
53 for (std::size_t i = 0; i < encoded->arraySize(); ++i) {
54 const auto &entry = encoded->at(i);
55 if (!entry.isString() || entry.asString().empty() || !seen.emplace(entry.asString()).second)
57 eve::DiagnosticCode::InvalidArgument, "tag values must be non-empty and unique", path));
58 result.push_back(entry.asString());
59 }
60 return eve::Result<std::vector<std::string>>::success(std::move(result));
61}
62
64 const std::string &path) {
65 if (!encoded || !encoded->isObject())
68 std::unordered_map<std::string, std::string> result;
69 for (const auto &key : encoded->keys()) {
70 const eve::Value *value = encoded->find(key);
71 if (key.empty() || !value || !value->isString())
73 eve::DiagnosticCode::InvalidArgument, "map keys and values must be strings", path));
74 result.emplace(key, value->asString());
75 }
76 return eve::Result<std::unordered_map<std::string, std::string>>::success(std::move(result));
77}
78
79eve::Result<ItemStack> decodeStack(const eve::Value &encoded, const std::string &path, int &largestId,
80 std::unordered_set<int> &instanceIds) {
81 if (!encoded.isObject())
83 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "item stack must be an object", path));
84 const eve::Value *instanceId = encoded.find("instanceId");
85 const eve::Value *itemId = encoded.find("itemId");
86 const eve::Value *quantity = encoded.find("quantity");
87 const eve::Value *durability = encoded.find("durability");
88 if (!instanceId || !instanceId->isInt64() || !itemId || !itemId->isString() || !quantity ||
89 !quantity->isInt64() || !durability || !durability->isNumeric() || !std::isfinite(number(*durability)))
91 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "item stack fields are invalid", path));
92
93 auto props = decodeMap(encoded.find("props"), path + ".props");
94 if (!props.ok()) return eve::Result<ItemStack>::failure(props.status());
95 auto tags = decodeStrings(encoded.find("tags"), path + ".tags");
96 if (!tags.ok()) return eve::Result<ItemStack>::failure(tags.status());
97
98 ItemStack stack;
99 stack.instanceId = static_cast<int>(instanceId->asInt());
100 stack.itemId = itemId->asString();
101 stack.quantity = static_cast<int>(quantity->asInt());
102 stack.durability = static_cast<float>(number(*durability));
103 stack.props = std::move(props).takeValue();
104 stack.tags = std::move(tags).takeValue();
105 if (stack.itemId.empty() || stack.quantity <= 0) {
106 if (stack.instanceId != 0 || !stack.itemId.empty() || stack.quantity != 0 || stack.durability != -1.f ||
107 !stack.props.empty() || !stack.tags.empty())
109 eve::DiagnosticCode::InvalidArgument, "empty item stack must use canonical empty values", path));
110 return eve::Result<ItemStack>::success(std::move(stack));
111 }
112
113 const ItemDefinition *definition = ItemRegistry::find(stack.itemId);
114 if (!definition)
117 "item definition referenced by snapshot is not registered", path + ".itemId"));
118 if (stack.instanceId <= 0 || stack.quantity > definition->maxStack || !instanceIds.emplace(stack.instanceId).second)
120 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "item identity or stack quantity is invalid", path));
121 largestId = std::max(largestId, stack.instanceId);
122 return eve::Result<ItemStack>::success(std::move(stack));
123}
124
125bool stackMatchesEquipmentSlot(const ItemStack &stack, const std::string &slot,
126 const std::vector<std::string> &allowedTags) {
127 if (stack.empty()) return true;
128 const ItemDefinition *definition = ItemRegistry::find(stack.itemId);
129 if (!definition || (!definition->equipSlot.empty() && definition->equipSlot != slot)) return false;
130 if (allowedTags.empty()) return true;
131 for (const auto &tag : allowedTags) {
132 if (definition->hasTag(tag) || stack.hasTag(tag)) return true;
133 }
134 return false;
135}
136
137} // namespace
138
139InventorySaveSession::PreparedRestore::PreparedRestore() = default;
141InventorySaveSession::PreparedRestore::PreparedRestore(PreparedRestore &&) noexcept = default;
142InventorySaveSession::PreparedRestore &InventorySaveSession::PreparedRestore::operator=(PreparedRestore &&) noexcept =
143 default;
144
145void InventorySaveSession::bind(Bag &bag, EquipmentSet &equipment) noexcept {
146 bag_ = &bag;
147 equipment_ = &equipment;
148}
149
151 if (!bag_ || !equipment_)
153 eve::DiagnosticCode::PreconditionViolation, "inventory save session requires bound participants", {}));
155 slots.reserve(bag_->slots_.size());
156 for (const auto &stack : bag_->slots_) slots.emplace_back(encodeStack(stack));
158 bag.emplace("acceptRule", eve::Value(bag_->acceptRule_));
159 bag.emplace("acceptTags", encodeStrings(bag_->acceptTags_));
160 bag.emplace("capacityPolicy", eve::Value(bag_->capacityPolicy_));
161 bag.emplace("extra", encodeMap(bag_->extra_));
162 bag.emplace("id", eve::Value(bag_->id_));
163 bag.emplace("kind", eve::Value(bag_->kind_));
164 bag.emplace("maxVolume", eve::Value(bag_->maxVolume_));
165 bag.emplace("maxWeight", eve::Value(bag_->maxWeight_));
166 bag.emplace("rejectTags", encodeStrings(bag_->rejectTags_));
167 bag.emplace("slots", eve::Value(std::move(slots)));
168 bag.emplace("stackRule", eve::Value(bag_->stackRule_));
169
170 eve::Value::Array equipmentSlots;
171 equipmentSlots.reserve(equipment_->order_.size());
172 for (const auto &name : equipment_->order_) {
173 const auto &slot = equipment_->slots_.at(name);
174 eve::Value::Object encoded;
175 encoded.emplace("allowedTags", encodeStrings(slot.allowedTags));
176 encoded.emplace("name", eve::Value(name));
177 encoded.emplace("stack", encodeStack(slot.stack));
178 equipmentSlots.emplace_back(std::move(encoded));
179 }
180 eve::Value::Object equipment;
181 equipment.emplace("id", eve::Value(equipment_->id_));
182 equipment.emplace("slots", eve::Value(std::move(equipmentSlots)));
183
185 root.emplace("bag", eve::Value(std::move(bag)));
186 root.emplace("equipment", eve::Value(std::move(equipment)));
187 root.emplace("schema", eve::Value("eve.inventory.save-session"));
188 root.emplace("version", eve::Value(1));
189 auto encoded = eve::Value(std::move(root)).toJson();
190 if (!encoded.ok()) return encoded;
191 auto validated = prepareRestoreSnapshotJson(encoded.value());
192 if (!validated.ok()) return eve::Result<std::string>::failure(validated.status());
193 return encoded;
194}
195
197 std::string_view json) const {
198 if (!bag_ || !equipment_)
200 eve::DiagnosticCode::PreconditionViolation, "inventory save session requires bound participants", {}));
202 auto parsed = eve::Value::fromJson(json);
203 if (!parsed.ok()) return eve::Result<PreparedRestore>::failure(parsed.status());
204 const eve::Value &root = parsed.value();
205 const eve::Value *schema = root.isObject() ? root.find("schema") : nullptr;
206 const eve::Value *version = root.isObject() ? root.find("version") : nullptr;
207 if (!schema || !schema->isString() || schema->asString() != "eve.inventory.save-session")
209 eve::DiagnosticCode::InvalidArgument, "snapshot does not belong to InventorySaveSession", "$.schema"));
210 if (!version || !version->isInt64() || version->asInt() != 1)
212 eve::DiagnosticCode::UnknownVersion, "unsupported inventory snapshot version", "$.version"));
213 const eve::Value *bag = root.find("bag");
214 const eve::Value *equipment = root.find("equipment");
215 if (!bag || !bag->isObject() || !equipment || !equipment->isObject())
217 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "inventory participants must be objects", "$"));
218
219 PreparedRestore candidate;
220 const eve::Value *bagId = bag->find("id");
221 const eve::Value *kind = bag->find("kind");
222 const eve::Value *maxWeight = bag->find("maxWeight");
223 const eve::Value *maxVolume = bag->find("maxVolume");
224 const eve::Value *acceptRule = bag->find("acceptRule");
225 const eve::Value *capacityPolicy = bag->find("capacityPolicy");
226 const eve::Value *stackRule = bag->find("stackRule");
227 const eve::Value *slots = bag->find("slots");
228 if (!bagId || !bagId->isString() || !kind || !kind->isString() || kind->asString().empty() ||
229 !maxWeight || !maxWeight->isNumeric() || !maxVolume || !maxVolume->isNumeric() ||
230 !std::isfinite(number(*maxWeight)) || number(*maxWeight) < 0.0 || !std::isfinite(number(*maxVolume)) ||
231 number(*maxVolume) < 0.0 || !acceptRule || !acceptRule->isString() || !capacityPolicy ||
232 !capacityPolicy->isString() || !stackRule || !stackRule->isString() || !slots || !slots->isArray())
234 eve::Diagnostic::error(eve::DiagnosticCode::ParseError, "bag configuration is invalid", "$.bag"));
235 if (!InventorySystem::hasAcceptRule(acceptRule->asString()) ||
236 !InventorySystem::hasCapacityPolicy(capacityPolicy->asString()) ||
239 eve::DiagnosticCode::NotFound, "bag snapshot references an unregistered policy", "$.bag"));
240 auto preparedBag = std::make_unique<Bag>(static_cast<int>(slots->arraySize()));
241 preparedBag->id_ = bagId->asString();
242 preparedBag->kind_ = kind->asString();
243 preparedBag->maxWeight_ = static_cast<float>(number(*maxWeight));
244 preparedBag->maxVolume_ = static_cast<float>(number(*maxVolume));
245 preparedBag->acceptRule_ = acceptRule->asString();
246 preparedBag->capacityPolicy_ = capacityPolicy->asString();
247 preparedBag->stackRule_ = stackRule->asString();
248 auto acceptTags = decodeStrings(bag->find("acceptTags"), "$.bag.acceptTags");
249 if (!acceptTags.ok()) return eve::Result<PreparedRestore>::failure(acceptTags.status());
250 auto rejectTags = decodeStrings(bag->find("rejectTags"), "$.bag.rejectTags");
251 if (!rejectTags.ok()) return eve::Result<PreparedRestore>::failure(rejectTags.status());
252 auto extra = decodeMap(bag->find("extra"), "$.bag.extra");
253 if (!extra.ok()) return eve::Result<PreparedRestore>::failure(extra.status());
254 preparedBag->acceptTags_ = std::move(acceptTags).takeValue();
255 preparedBag->rejectTags_ = std::move(rejectTags).takeValue();
256 preparedBag->extra_ = std::move(extra).takeValue();
257
258 std::unordered_set<int> instanceIds;
259 for (std::size_t index = 0; index < slots->arraySize(); ++index) {
260 auto stack = decodeStack(slots->at(index), "$.bag.slots[" + std::to_string(index) + "]",
261 candidate.largestInstanceId, instanceIds);
262 if (!stack.ok()) return eve::Result<PreparedRestore>::failure(stack.status());
263 preparedBag->slots_[index] = std::move(stack).takeValue();
264 }
265
266 const eve::Value *equipmentId = equipment->find("id");
267 const eve::Value *equipmentSlots = equipment->find("slots");
268 if (!equipmentId || !equipmentId->isString() || !equipmentSlots || !equipmentSlots->isArray())
270 eve::DiagnosticCode::ParseError, "equipment configuration is invalid", "$.equipment"));
271 auto preparedEquipment = std::make_unique<EquipmentSet>();
272 preparedEquipment->id_ = equipmentId->asString();
273 std::unordered_set<std::string> slotNames;
274 for (std::size_t index = 0; index < equipmentSlots->arraySize(); ++index) {
275 const eve::Value &encoded = equipmentSlots->at(index);
276 const eve::Value *name = encoded.isObject() ? encoded.find("name") : nullptr;
277 const eve::Value *stackValue = encoded.isObject() ? encoded.find("stack") : nullptr;
278 if (!name || !name->isString() || name->asString().empty() ||
279 !slotNames.emplace(name->asString()).second || !stackValue)
281 eve::DiagnosticCode::InvalidArgument, "equipment slot identity is invalid", "$.equipment.slots"));
282 auto allowed = decodeStrings(encoded.find("allowedTags"), "$.equipment.slots.allowedTags");
283 if (!allowed.ok()) return eve::Result<PreparedRestore>::failure(allowed.status());
284 auto stack = decodeStack(*stackValue, "$.equipment.slots[" + std::to_string(index) + "].stack",
285 candidate.largestInstanceId, instanceIds);
286 if (!stack.ok()) return eve::Result<PreparedRestore>::failure(stack.status());
287 const std::string slotName = name->asString();
288 auto allowedTags = std::move(allowed).takeValue();
289 auto stackState = std::move(stack).takeValue();
290 if (!stackMatchesEquipmentSlot(stackState, slotName, allowedTags))
292 eve::DiagnosticCode::Conflict, "equipped item violates its slot contract", "$.equipment.slots"));
293 preparedEquipment->defineSlot(slotName);
294 preparedEquipment->slots_.at(slotName).allowedTags = std::move(allowedTags);
295 preparedEquipment->slots_.at(slotName).stack = std::move(stackState);
296 }
297 candidate.bag_ = std::move(preparedBag);
298 candidate.equipment_ = std::move(preparedEquipment);
299 return eve::Result<PreparedRestore>::success(std::move(candidate));
300}
301
303 bag_->id_.swap(prepared.bag_->id_);
304 bag_->kind_.swap(prepared.bag_->kind_);
305 std::swap(bag_->maxWeight_, prepared.bag_->maxWeight_);
306 std::swap(bag_->maxVolume_, prepared.bag_->maxVolume_);
307 bag_->acceptRule_.swap(prepared.bag_->acceptRule_);
308 bag_->capacityPolicy_.swap(prepared.bag_->capacityPolicy_);
309 bag_->stackRule_.swap(prepared.bag_->stackRule_);
310 bag_->acceptTags_.swap(prepared.bag_->acceptTags_);
311 bag_->rejectTags_.swap(prepared.bag_->rejectTags_);
312 bag_->slots_.swap(prepared.bag_->slots_);
313 bag_->extra_.swap(prepared.bag_->extra_);
314 equipment_->id_.swap(prepared.equipment_->id_);
315 equipment_->slots_.swap(prepared.equipment_->slots_);
316 equipment_->order_.swap(prepared.equipment_->order_);
317 InventorySystem::ensureNextInstanceIdAbove(prepared.largestInstanceId);
318}
319
321 auto prepared = prepareRestoreSnapshotJson(json);
322 if (!prepared.ok()) return eve::Result<void>::failure(prepared.status());
323 commitPrepared(std::move(prepared).takeValue());
325}
326
327} // namespace eve::inventory
double value
LogicalId itemId
int root
Definition AnimSmr.cpp:119
std::map< std::string, Var > values
std::uint32_t key
Versioned atomic persistence boundary for one bag and equipment set.
TokenKind kind
std::string name
const std::string * tag
std::string path
Definition PlayHost.cpp:110
double number
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
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
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
格子型物品容器(脚本可直接操作)。
Definition Bag.h:21
EVENGINE_API_FOUNDATION public API.
Definition Equipment.h:19
Validated owning inventory state awaiting publication.
Coordinates persistent inventory owners without duplicating their live state.
void commitPrepared(PreparedRestore prepared) noexcept
Publish a previously validated state without failure or callbacks.
eve::Result< std::string > snapshotJson() const
Capture the complete bag and equipment state.
eve::Result< PreparedRestore > prepareRestoreSnapshotJson(std::string_view json) const
Parse and validate a snapshot without mutating either participant.
eve::Result< void > restoreSnapshotJson(std::string_view json)
Validate and atomically restore both bound inventory participants.
static void ensureBuiltins()
确保内置规则已注册(模块首次使用时自动调用)。
static bool hasStackRule(const std::string &name)
True when stack rule.
static bool hasAcceptRule(const std::string &name)
True when accept rule.
static bool hasCapacityPolicy(const std::string &name)
True when capacity policy.
static const ItemDefinition * find(const std::string &id)
Finds find.
Definition Item.cpp:53
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
运行时容器:固定格数的背包 / 箱子 / 商店栏等。 行为由 InventorySystem 提供;本类暴露便于脚本绑定的薄封装方法。
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16