7#include <unordered_map>
8#include <unordered_set>
15std::vector<ShopOffer> &offers() {
16 static std::vector<ShopOffer>
value;
20std::unordered_map<std::string, std::size_t> &offerIndex() {
21 static std::unordered_map<std::string, std::size_t>
value;
25bool validId(
const std::string &
value) {
26 if (
value.empty() ||
value.size() > 256)
return false;
27 for (
unsigned char ch :
value)
28 if (ch < 0x20 || ch == 0x7f) return false;
35 "rpg.shop-catalogue"));
39 static const std::unordered_set<std::string> known = {
40 "id",
"itemId",
"name",
"desc",
"buyPrice",
"sellPrice"};
42 if (known.
count(
key) == 0) return false;
49 std::string parseError;
51 if (!document.valid())
return failure(parseError.empty() ?
"invalid JSON" : parseError,
"$");
52 const auto root = document.root();
53 if (!
root.isArray() ||
root.size() == 0)
54 return failure(
"shop catalogue must be a non-empty array",
"$");
56 std::vector<ShopOffer> proposed;
57 std::unordered_map<std::string, std::size_t> proposedIndex;
58 proposed.reserve(
root.size());
61 const std::string
path =
"$[" + std::to_string(
index) +
"]";
62 if (!
object.isObject())
return failure(
"shop offer must be an object",
path);
63 if (!onlyKnownFields(
object))
return failure(
"shop offer contains an unknown field",
path);
64 const auto id =
object.get(
"id");
65 const auto itemIdValue =
object.get(
"itemId");
66 const auto name =
object.get(
"name");
68 const auto buyPrice =
object.get(
"buyPrice");
69 const auto sellPrice =
object.get(
"sellPrice");
70 if (!
id.isString() || !validId(
id.asString()))
71 return failure(
"offer id must be a stable non-empty id",
path +
".id");
72 const std::string
itemId = itemIdValue.isString() ? itemIdValue.asString() : std::string{};
73 if (!validId(
itemId))
return failure(
"itemId must be a stable non-empty id",
path +
".itemId");
75 return failure(
"shop offer references an unknown inventory item",
path +
".itemId");
76 if (!
name.isString() ||
name.asString().empty() ||
name.asString().size() > 512)
77 return failure(
"name must be a non-empty string of at most 512 bytes",
path +
".name");
79 return failure(
"desc must be a string of at most 2048 bytes",
path +
".desc");
80 if (!buyPrice.isInt64() || buyPrice.asInt64() < 0 || buyPrice.asInt64() > INT_MAX)
81 return failure(
"buyPrice must be a non-negative integer",
path +
".buyPrice");
82 if (!sellPrice.isInt64() || sellPrice.asInt64() < 0 || sellPrice.asInt64() > buyPrice.asInt64())
83 return failure(
"sellPrice must be a non-negative integer no greater than buyPrice",
85 if (proposedIndex.count(
id.asString()) != 0)
86 return failure(
"duplicate offer id",
path +
".id");
88 offer.
id =
id.asString();
92 offer.
buyPrice =
static_cast<int>(buyPrice.asInt64());
93 offer.
sellPrice =
static_cast<int>(sellPrice.asInt64());
94 proposedIndex.emplace(offer.
id, proposed.size());
95 proposed.push_back(std::move(offer));
97 offers() = std::move(proposed);
98 offerIndex() = std::move(proposedIndex);
104 offerIndex().clear();
110 const auto found = offerIndex().find(
id);
111 return found == offerIndex().end() ? nullptr : &offers()[
found->second];
115 return index < 0 || index >=
count() ? nullptr : &offers()[
static_cast<std::size_t
>(
index)];
Strict, atomically replaceable RPG shop content.
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static const ItemDefinition * find(const std::string &id)
Finds find.
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
EVENGINE_API_FOUNDATION public API.
static eve::Result< int > replaceFromJsonStrict(const std::string &json)
Strictly validate and atomically replace the entire offer catalogue.
static const ShopOffer * find(const std::string &id)
Find an offer by stable offer id, or null when absent.
static const ShopOffer * at(int index)
Return an offer by deterministic source index, or null when out of range.
static void clear()
Remove all offers. Intended for teardown and isolated tests.
static int count()
Return the number of committed offers in deterministic source order.
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
One immutable offer in the process-local shop catalogue.