3#if defined(__EMSCRIPTEN__)
10 "material Catalog JSON codec is unavailable on the Web build",
"codec",
11 {},
"pixelworld.catalog-codec");
28#include <Poco/Dynamic/Var.h>
29#include <Poco/JSON/Array.h>
30#include <Poco/JSON/Object.h>
31#include <Poco/JSON/Parser.h>
32#include <Poco/JSON/Stringifier.h>
39#include <unordered_set>
44using Object = Poco::JSON::Object;
45using Array = Poco::JSON::Array;
50 "pixelworld.catalog-codec"));
53bool exactFields(
const Object&
object, std::initializer_list<std::string_view> allowed) {
54 std::unordered_set<std::string> names;
55 for (
const auto name : allowed) names.emplace(
name);
56 if (
object.
size() != names.size())
return false;
58 if (!names.contains(
name)) return false;
59 for (
const auto&
name : names)
89 static_assert(std::is_integral_v<T> && !std::is_same_v<T, bool>);
90 if (!
object.has(
key) ||
object.isNull(
key))
return false;
92 const auto wide =
object.get(
key).convert<std::int64_t>();
93 if constexpr (std::is_unsigned_v<T>) {
94 if (wide < 0 || std::uint64_t(wide) > std::numeric_limits<T>::max())
return false;
95 }
else if (wide < std::numeric_limits<T>::min() ||
96 wide > std::numeric_limits<T>::max()) {
99 result =
static_cast<T
>(wide);
106Object::Ptr materialJson(
const MaterialDefinition&
material) {
107 Object::Ptr out(
new Object);
108 out->set(
"id", std::uint16_t(
material.id));
111 out->set(
"density",
material.density);
112 out->set(
"viscosity",
material.viscosity);
113 out->set(
"thermalConductivity",
material.thermalConductivity);
114 out->set(
"heatCapacity",
material.heatCapacity);
115 out->set(
"ignitionTemperature",
material.ignitionTemperature);
116 out->set(
"meltingTemperature",
material.meltingTemperature);
117 out->set(
"boilingTemperature",
material.boilingTemperature);
118 out->set(
"defaultLifetime",
material.defaultLifetime);
119 out->set(
"flammable",
material.flammable);
120 out->set(
"defaultTemperature",
material.defaultTemperature);
121 out->set(
"blastResistance",
material.blastResistance);
122 out->set(
"displayRgba",
material.displayRgba);
123 Array::Ptr tags(
new Array);
125 out->set(
"tags", tags);
133 root->set(
"schema",
"eve.pixelworld.material-catalog");
134 root->set(
"version", 1);
135 Array::Ptr materials(
new Array);
137 root->set(
"materials", materials);
139 for (
const auto& reaction : catalog.
reactions()) {
141 value->set(
"id", reaction.id);
142 value->set(
"first", std::uint16_t(reaction.first));
143 value->set(
"second", std::uint16_t(reaction.second));
144 value->set(
"firstResult", std::uint16_t(reaction.firstResult));
145 value->set(
"secondResult", std::uint16_t(reaction.secondResult));
146 value->set(
"minimumTemperature", reaction.minimumTemperature);
147 value->set(
"heatDelta", reaction.heatDelta);
148 value->set(
"priority", reaction.priority);
152 Array::Ptr phases(
new Array);
156 value->set(
"source", std::uint16_t(
phase.source));
157 value->set(
"result", std::uint16_t(
phase.result));
162 value->set(
"temperatureDelta",
phase.temperatureDelta);
166 root->set(
"phaseRules", phases);
167 std::ostringstream
text;
168 Poco::JSON::Stringifier::stringify(Poco::Dynamic::Var(
root),
text, 2, -1);
175 root = Poco::JSON::Parser().parse(std::string(json)).extract<Object::Ptr>();
177 return malformed(
"material Catalog JSON is malformed");
179 if (!
root || !exactFields(*
root, {
"schema",
"version",
"materials",
"reactions",
"phaseRules"}))
180 return malformed(
"root contains missing or unknown fields");
181 if (
root->optValue<std::string>(
"schema",
"") !=
"eve.pixelworld.material-catalog")
182 return malformed(
"unknown material Catalog schema",
"schema");
184 if (!readNumber(*
root,
"version", version) || version != 1)
185 return malformed(
"unknown material Catalog version",
"version");
186 Array::Ptr materials;
190 materials =
root->getArray(
"materials");
192 phases =
root->getArray(
"phaseRules");
194 return malformed(
"material Catalog arrays are invalid");
196 if (!materials || !
reactions || !phases || materials->size() > 65535U ||
197 reactions->size() > 1'000'000U || phases->size() > 1'000'000U)
198 return malformed(
"material Catalog arrays are missing or exceed their budgets");
205 value = materials->getObject(
unsigned(
index));
207 return malformed(
"material entry is not an object",
208 "materials[" + std::to_string(
index) +
"]");
210 if (!
value || !exactFields(*
value, {
"id",
"name",
"state",
"density",
"viscosity",
211 "thermalConductivity",
"heatCapacity",
"ignitionTemperature",
212 "meltingTemperature",
"boilingTemperature",
"defaultLifetime",
213 "flammable",
"defaultTemperature",
"blastResistance",
214 "displayRgba",
"tags"}))
215 return malformed(
"material contains missing or unknown fields",
216 "materials[" + std::to_string(
index) +
"]");
218 std::uint16_t
id = 0;
219 if (!readNumber(*
value,
"id",
id) || !readNumber(*
value,
"density",
material.density) ||
221 !readNumber(*
value,
"thermalConductivity",
material.thermalConductivity) ||
222 !readNumber(*
value,
"heatCapacity",
material.heatCapacity) ||
223 !readNumber(*
value,
"ignitionTemperature",
material.ignitionTemperature) ||
224 !readNumber(*
value,
"meltingTemperature",
material.meltingTemperature) ||
225 !readNumber(*
value,
"boilingTemperature",
material.boilingTemperature) ||
226 !readNumber(*
value,
"defaultLifetime",
material.defaultLifetime) ||
227 !readNumber(*
value,
"defaultTemperature",
material.defaultTemperature) ||
228 !readNumber(*
value,
"blastResistance",
material.blastResistance) ||
230 return malformed(
"material contains an invalid number",
231 "materials[" + std::to_string(
index) +
"]");
234 if (!parseState(
value->optValue<std::string>(
"state",
""),
material.state))
235 return malformed(
"material state is unknown",
236 "materials[" + std::to_string(
index) +
"].state");
237 if (!
value->has(
"flammable") || !
value->has(
"tags"))
238 return malformed(
"material fields are missing",
239 "materials[" + std::to_string(
index) +
"]");
242 Array::Ptr tags =
value->getArray(
"tags");
243 if (!tags || tags->size() > 1024U)
throw std::runtime_error(
"tags");
244 for (std::size_t
tag = 0;
tag < tags->size(); ++
tag)
245 material.tags.push_back(tags->getElement<std::string>(
unsigned(
tag)));
247 return malformed(
"material tags or flammable value are invalid",
248 "materials[" + std::to_string(
index) +
"]");
253 std::vector<MaterialReactionRule> reactionRules;
254 reactionRules.reserve(
reactions->size());
260 return malformed(
"reaction entry is not an object",
261 "reactions[" + std::to_string(
index) +
"]");
263 if (!
value || !exactFields(*
value, {
"id",
"first",
"second",
"firstResult",
"secondResult",
264 "minimumTemperature",
"heatDelta",
"priority"}))
265 return malformed(
"reaction contains missing or unknown fields",
266 "reactions[" + std::to_string(
index) +
"]");
268 std::uint16_t
first = 0,
second = 0, firstResult = 0, secondResult = 0;
269 rule.
id =
value->optValue<std::string>(
"id",
"");
271 !readNumber(*
value,
"firstResult", firstResult) ||
272 !readNumber(*
value,
"secondResult", secondResult) ||
276 return malformed(
"reaction contains an invalid number",
277 "reactions[" + std::to_string(
index) +
"]");
282 reactionRules.push_back(std::move(rule));
285 std::vector<MaterialPhaseRule> phaseRules;
286 phaseRules.reserve(phases->size());
292 return malformed(
"phase rule entry is not an object",
293 "phaseRules[" + std::to_string(
index) +
"]");
295 if (!
value || !exactFields(*
value, {
"id",
"source",
"result",
"direction",
"threshold",
296 "temperatureDelta",
"priority"}))
297 return malformed(
"phase rule contains missing or unknown fields",
298 "phaseRules[" + std::to_string(
index) +
"]");
300 std::uint16_t
source = 0, result = 0;
301 rule.
id =
value->optValue<std::string>(
"id",
"");
302 const std::string
direction =
value->optValue<std::string>(
"direction",
"");
305 else return malformed(
"phase direction is unknown",
306 "phaseRules[" + std::to_string(
index) +
"].direction");
307 if (!readNumber(*
value,
"source",
source) || !readNumber(*
value,
"result", result) ||
311 return malformed(
"phase rule contains an invalid number",
312 "phaseRules[" + std::to_string(
index) +
"]");
315 phaseRules.push_back(std::move(rule));
318 std::move(phaseRules));
RoadLaneDirection direction
Battle::Reactions reactions
const UnitySourceAsset & source
const std::map< std::string, Value > & definitions
A structured explanation of a failed, degraded, or noteworthy result.
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.
Owning immutable material and reaction table used by one PixelWorld.
std::span< const MaterialReactionRule > reactions() const noexcept
Borrow immutable canonical-priority reaction rules.
std::span< const MaterialDefinition > definitions() const noexcept
Borrow immutable definitions until this catalog is destroyed.
std::span< const MaterialPhaseRule > phaseRules() const noexcept
Borrow immutable canonical-priority phase rules.
static eve::Result< MaterialCatalog > create(std::vector< MaterialDefinition > definitions, std::vector< MaterialReactionRule > reactions={}, std::vector< MaterialPhaseRule > phaseRules={})
Validate and own a complete catalog candidate transactionally.
std::string stateName(OrderState state)
Returns the stable lowercase name of a state.
eve::Result< MaterialCatalog > decodeMaterialCatalogJson(std::string_view json)
Decode and validate schema eve.pixelworld.material-catalog version 1.
MaterialId
Compact stable material identifier stored in each authoritative cell.
MaterialState
Broad movement class for one pixel material.
eve::Result< std::string > encodeMaterialCatalogJson(const MaterialCatalog &catalog)
Encode schema eve.pixelworld.material-catalog version 1 canonically.
bool readNumber(const EditorValue &value, const char *name, float &output)
enum EVENGINE_API_FOUNDATION Array
OT_ARRAY (editable via Runtime array APIs / ReflectedPropertyModel).
Immutable validated properties for one catalog material.
Data-driven phase transition evaluated after heat diffusion.
std::int16_t temperatureDelta
TemperatureDirection direction
Deterministically ordered binary material reaction.
std::int16_t minimumTemperature