From fa5603f4353bf52eff6c94acbc73b73f8f7daa98 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 6 Sep 2026 15:42:03 +0200 Subject: [PATCH] feat: add NamedDimension and parseDimensionName --- core/src/Advancement.cpp | 174 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 core/src/Advancement.cpp diff --git a/core/src/Advancement.cpp b/core/src/Advancement.cpp new file mode 100644 index 0000000..ebb5a8b --- /dev/null +++ b/core/src/Advancement.cpp @@ -0,0 +1,174 @@ +#include "vf/Advancement.h" + +#include + +#include "vf/Command.h" +#include "vf/PotionCatalog.h" + +namespace vf { + +namespace { + +struct NamedDimension { + const char* name; + DimensionId id; +}; + +// Kept in step with DimensionConfig::nameOf, which Dimension.cpp cannot hand +// back a parser for without dragging every world generator into the link. +const NamedDimension dimensionNames[] = { + {"overworld", DimensionId::Overworld}, + {"nether", DimensionId::Nether}, + {"end", DimensionId::End}, +}; + +bool parseDimensionName(const std::string& text, DimensionId& out) { + for (const NamedDimension& entry : dimensionNames) { + if (text == entry.name) { + out = entry.id; + return true; + } + } + return false; +} + +bool parsePotionName(const std::string& text, PotionKind& out) { + const PotionCatalog& catalog = potionCatalog(); + for (const PotionEntry& entry : catalog.entries()) { + if (text == entry.name) { + out = entry.kind; + return true; + } + } + return false; +} + +bool parseProfessionName(const std::string& text, VillagerProfession& out) { + for (int i = 0; i < static_cast(VillagerProfession::Count); ++i) { + VillagerProfession profession = static_cast(i); + if (text == villagerProfessionName(profession)) { + out = profession; + return true; + } + } + return false; +} + +bool readOptionalResource(const JsonValue& owner, const std::string& key, + const std::string& defaultSpace, ResourceId& out, bool& present, + std::string& problem) { + const JsonValue* field = owner.find(key); + present = false; + if (field == nullptr) return true; + if (!field->isString()) { + problem = "\"" + key + "\" must be a string"; + return false; + } + if (!ResourceId::parse(field->stringValue(), defaultSpace, out)) { + problem = "\"" + key + "\" is not a valid resource id: " + field->stringValue(); + return false; + } + present = true; + return true; +} + +bool readResourceList(const JsonValue& owner, const std::string& key, + const std::string& defaultSpace, std::vector& out, + std::string& problem) { + const JsonValue* list = owner.find(key); + if (list == nullptr) return true; + if (!list->isArray()) { + problem = "\"" + key + "\" must be an array of resource ids"; + return false; + } + for (const JsonValue& element : list->elements()) { + ResourceId id; + if (!element.isString() || !ResourceId::parse(element.stringValue(), defaultSpace, id)) { + problem = "\"" + key + "\" holds something that is not a resource id"; + return false; + } + out.push_back(id); + } + return true; +} + +bool parseCriterionJson(const std::string& criterionName, const JsonValue& root, + const std::string& defaultSpace, AdvancementCriterion& out, + std::string& problem) { + if (!root.isObject()) { + problem = "criterion \"" + criterionName + "\" must be an object"; + return false; + } + const JsonValue* trigger = root.find("trigger"); + if (trigger == nullptr || !trigger->isString() || + !parseAdvancementTriggerName(trigger->stringValue(), out.trigger)) { + problem = "criterion \"" + criterionName + "\" needs a known \"trigger\""; + return false; + } + out.name = criterionName; + out.requiredCount = static_cast(root.intOr("count", 1)); + if (out.requiredCount < 1) { + problem = "criterion \"" + criterionName + "\" needs a count of at least 1"; + return false; + } + + ResourceId id; + bool present = false; + if (!readOptionalResource(root, "item", defaultSpace, id, present, problem)) return false; + if (present) { + if (!resolveItemId(id, out.item)) { + problem = "criterion \"" + criterionName + "\": no such item " + id.text(); + return false; + } + out.matchMask |= AdvancementCriterion::MatchItem; + } + if (!readOptionalResource(root, "block", defaultSpace, id, present, problem)) return false; + if (present) { + if (!resolveBlockId(id, out.block)) { + problem = "criterion \"" + criterionName + "\": no such block " + id.text(); + return false; + } + out.matchMask |= AdvancementCriterion::MatchBlock; + } + if (!readOptionalResource(root, "tool", defaultSpace, id, present, problem)) return false; + if (present) { + if (!resolveItemId(id, out.tool)) { + problem = "criterion \"" + criterionName + "\": no such tool " + id.text(); + return false; + } + out.matchMask |= AdvancementCriterion::MatchTool; + } + + if (const JsonValue* species = root.find("species")) { + if (!species->isString() || !parseSpeciesName(species->stringValue(), out.species)) { + problem = "criterion \"" + criterionName + "\": unknown species"; + return false; + } + out.matchMask |= AdvancementCriterion::MatchSpecies; + } + if (const JsonValue* dimension = root.find("dimension")) { + if (!dimension->isString() || + !parseDimensionName(dimension->stringValue(), out.dimension)) { + problem = "criterion \"" + criterionName + "\": unknown dimension"; + return false; + } + out.matchMask |= AdvancementCriterion::MatchDimension; + } + if (const JsonValue* potion = root.find("potion")) { + if (!potion->isString() || !parsePotionName(potion->stringValue(), out.potion)) { + problem = "criterion \"" + criterionName + "\": unknown potion"; + return false; + } + out.matchMask |= AdvancementCriterion::MatchPotion; + } + if (const JsonValue* profession = root.find("profession")) { + if (!profession->isString() || + !parseProfessionName(profession->stringValue(), out.profession)) { + problem = "criterion \"" + criterionName + "\": unknown villager profession"; + return false; + } + out.matchMask |= AdvancementCriterion::MatchProfession; + } + return true; +} +