载入中...
搜索中...
未找到
RTSContent.cpp
浏览该文件的文档.
1#include "rts/RTSContent.h"
2
3#include "common/Value.h"
4
5#include <algorithm>
6#include <array>
7#include <set>
8#include <string>
9#include <utility>
10#include <vector>
11
12namespace eve::rts {
13namespace {
14
15struct StagedDefinition {
16 std::string type;
17 std::string id;
18 Value value;
19};
20
21const Value* field(const Value::Object& object, std::string_view name) {
22 const auto it = object.find(std::string(name));
23 return it == object.end() ? nullptr : &it->second;
24}
25
26std::string text(const Value::Object& object, std::string_view name) {
27 const auto* value = field(object, name);
28 const auto* result = value == nullptr ? nullptr : value->getIf<std::string>();
29 return result == nullptr ? std::string{} : *result;
30}
31
32double number(const Value::Object& object, std::string_view name, double fallback) {
33 const auto* value = field(object, name);
34 if (value == nullptr) return fallback;
35 if (const auto* real = value->getIf<double>()) return *real;
36 if (const auto* integer = value->getIf<std::int64_t>()) return static_cast<double>(*integer);
37 return fallback;
38}
39
40void normalizeWeapon(Value::Object& object) {
41 if (field(object, "kind") == nullptr) object.emplace("kind", Value("ranged"));
42 const double speed = number(object, "projectileSpeed", 0.0);
43 if (field(object, "logic") == nullptr) object.emplace("logic", Value(speed > 0.0 ? "projectile" : "hitscan"));
44 if (field(object, "ammo") == nullptr) {
45 const int magazine = std::max(1, static_cast<int>(number(object, "magazineSize", 0.0)));
46 const int reserve = number(object, "magazineSize", 0.0) > 0.0 ? 0 : -1;
47 object.emplace("ammo", Value(Value::Object{{"mag", Value(magazine)},
48 {"reload", Value(number(object, "reloadTime", 1.0))},
49 {"reserve", Value(reserve)}}));
50 }
51 if (field(object, "projectile") == nullptr && speed > 0.0) {
52 object.emplace("projectile", Value(Value::Object{{"aoe", Value(number(object, "splashRadius", 0.0))},
53 {"pelletCount", Value(1)},
54 {"pelletSpread", Value(0.0)},
55 {"speed", Value(speed)},
56 {"type", Value("rts")}}));
57 }
58 const std::string targets = text(object, "targets");
59 if (field(object, "targetsGround") == nullptr)
60 object.emplace("targetsGround", Value(targets != "air"));
61 if (field(object, "targetsAir") == nullptr)
62 object.emplace("targetsAir", Value(targets != "ground"));
63}
64
65bool available(const definitions::DefinitionRegistry& registry, const std::vector<StagedDefinition>& staged,
66 std::string_view type, std::string_view id) {
67 if (id.empty()) return true;
68 if (std::any_of(staged.begin(), staged.end(), [&](const auto& value) {
69 return value.type == type && value.id == id;
70 })) return true;
71 auto resolved = registry.resolve(std::string(type), std::string(id));
72 if (resolved) return true;
73 resolved.ignore("optional RTS content cross-reference lookup");
74 return false;
75}
76
77Result<void> requireReference(const definitions::DefinitionRegistry& registry,
78 const std::vector<StagedDefinition>& staged, const StagedDefinition& owner,
79 const Value::Object& object, std::string_view member, std::string_view targetType) {
80 const auto* source = field(object, member);
82 const auto* id = source->getIf<std::string>();
83 if (id == nullptr)
85 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS content reference must be a string",
86 owner.type + "." + owner.id + "." + std::string(member), {}, "rts.content"));
87 if (!available(registry, staged, targetType, *id))
90 "RTS content references an unknown " + std::string(targetType) + " '" + *id + "'",
91 owner.type + "." + owner.id + "." + std::string(member), {}, "rts.content"));
93}
94
95} // namespace
96
98 std::string_view json) {
99 auto parsed = Value::fromJson(json);
100 if (!parsed) return Result<ContentImportReceipt>::failure(parsed.status());
101 Value root = std::move(parsed).takeValue();
102 const auto* object = root.getIf<Value::Object>();
103 if (object == nullptr)
105 DiagnosticCode::InvalidArgument, "RTS content root must be an object", "root", {}, "rts.content"));
106 static constexpr std::array<std::pair<std::string_view, std::string_view>, 7> arrays{{
107 {"weapons", "weapon"}, {"units", "unit"}, {"buildings", "building"}, {"upgrades", "upgrade"},
108 {"statusEffects", "effect"}, {"abilities", "ability"}, {"damageMultipliers", "damage_multiplier"},
109 }};
110 std::vector<StagedDefinition> staged;
111 std::set<std::pair<std::string, std::string>> keys;
112 for (const auto& [member, type] : arrays) {
113 const auto* value = field(*object, member);
114 if (value == nullptr) continue;
115 const auto* entries = value->getIf<Value::Array>();
116 if (entries == nullptr)
118 "RTS content collection must be an array",
119 std::string(member), {}, "rts.content"));
120 for (std::size_t index = 0; index < entries->size(); ++index) {
121 const auto* item = (*entries)[index].getIf<Value::Object>();
122 if (item == nullptr)
124 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS content item must be an object",
125 std::string(member) + "[" + std::to_string(index) + "]", {}, "rts.content"));
126 Value::Object normalized = *item;
127 std::string id = text(normalized, "id");
128 if (type == "damage_multiplier" && id.empty()) {
129 const std::string damage = text(normalized, "damageType");
130 const std::string armor = text(normalized, "armorType");
131 if (!damage.empty() && !armor.empty()) {
132 id = damage + "." + armor;
133 normalized.emplace("id", Value(id));
134 }
135 }
136 if (id.empty())
138 Diagnostic::error(DiagnosticCode::InvalidArgument, "RTS content item requires a non-empty id",
139 std::string(member) + "[" + std::to_string(index) + "].id", {}, "rts.content"));
140 if (!keys.emplace(std::string(type), id).second)
142 DiagnosticCode::InvalidArgument, "RTS content pack contains a duplicate definition",
143 std::string(type) + ":" + id, {}, "rts.content"));
144 if (type == "weapon") normalizeWeapon(normalized);
145 staged.push_back({std::string(type), std::move(id), Value(std::move(normalized))});
146 }
147 }
148 for (const auto& definition : staged) {
149 const auto* item = definition.value.getIf<Value::Object>();
150 if (definition.type == "unit") {
151 for (const auto& [member, target] : {std::pair{"weaponType", "weapon"},
152 {"producer", "building"}, {"prerequisite", "building"}}) {
153 auto checked = requireReference(registry, staged, definition, *item, member, target);
154 if (!checked) return Result<ContentImportReceipt>::failure(checked.status());
155 }
156 } else if (definition.type == "building") {
157 for (const auto& [member, target] : {std::pair{"weaponType", "weapon"},
158 {"prerequisite", "building"}}) {
159 auto checked = requireReference(registry, staged, definition, *item, member, target);
160 if (!checked) return Result<ContentImportReceipt>::failure(checked.status());
161 }
162 } else if (definition.type == "upgrade") {
163 for (const auto& [member, target] : {std::pair{"producer", "building"},
164 {"prerequisiteBuilding", "building"},
165 {"prerequisiteUpgrade", "upgrade"},
166 {"targetUnit", "unit"}, {"targetBuilding", "building"}}) {
167 auto checked = requireReference(registry, staged, definition, *item, member, target);
168 if (!checked) return Result<ContentImportReceipt>::failure(checked.status());
169 }
170 } else if (definition.type == "ability") {
171 for (const auto& [member, target] : {std::pair{"casterUnit", "unit"},
172 {"statusEffect", "effect"}}) {
173 auto checked = requireReference(registry, staged, definition, *item, member, target);
174 if (!checked) return Result<ContentImportReceipt>::failure(checked.status());
175 }
176 }
177 }
178
179 const std::string rollback = registry.snapshotJson();
180 ContentImportReceipt receipt;
181 for (const auto& definition : staged) {
182 auto encoded = definition.value.toJson();
183 if (!encoded) {
184 auto restored = registry.restoreJson(rollback);
185 if (!restored) return Result<ContentImportReceipt>::failure(restored.status());
186 return Result<ContentImportReceipt>::failure(encoded.status());
187 }
188 auto existing = registry.handle(definition.type, definition.id);
189 const bool replace = existing.ok();
190 if (!replace) existing.ignore("RTS content insert lookup");
191 auto published = replace ? registry.replace(definition.type, definition.id, 1, encoded.value())
192 : registry.insert(definition.type, definition.id, 1, encoded.value());
193 if (!published) {
194 const Status failure = published.status();
195 auto restored = registry.restoreJson(rollback);
196 if (!restored) return Result<ContentImportReceipt>::failure(restored.status());
198 }
199 replace ? ++receipt.replaced : ++receipt.inserted;
200 }
203}
204
205} // namespace eve::rts
LogicalId target
double value
int root
Definition AnimSmr.cpp:119
std::unordered_map< std::string, QuestRuntime > entries
std::string text
std::string name
std::string id
Definition PlayHost.cpp:108
Atomic RTS content-pack import into the canonical definition registry.
double number
float(ui::Theme::* member)[4]
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
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
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
Deterministic registry of topic-neutral, versioned JSON definitions.
Definition Definitions.h:58
eve::Result< DefinitionHandle > insert(const std::string &type, const std::string &id, int version, const std::string &json)
Inserts a definition using the common generation-qualified registry.
std::string snapshotJson() const
Exports all registry state as deterministic canonical JSON.
eve::Result< DefinitionHandle > replace(const std::string &type, const std::string &id, int version, const std::string &json)
Replaces a live definition and invalidates its previous handle.
eve::Result< DefinitionHandle > handle(const std::string &type, const std::string &id) const
Returns the current generation-qualified handle through Result.
eve::Result< void > restoreJson(const std::string &json)
Transactionally restores registry state from snapshotJson() output.
static Result< ContentImportReceipt > load(definitions::DefinitionRegistry &registry, std::string_view json)
Parse, cross-validate, normalize weapons, and atomically import one JSON pack.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
const EditorValue * field(const EditorValue &value, const char *name)
Receipt for one atomically imported RTS content pack.
Definition RTSContent.h:19