From 570172c86dc7e654729722caab79d71f6f906c8f Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 7 Sep 2026 10:23:01 +0200 Subject: [PATCH] feat: add endsWith and splitDataPath --- core/src/DataPack.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 core/src/DataPack.cpp diff --git a/core/src/DataPack.cpp b/core/src/DataPack.cpp new file mode 100644 index 0000000..cfcfd3e --- /dev/null +++ b/core/src/DataPack.cpp @@ -0,0 +1,159 @@ +#include "vf/DataPack.h" + +#include +#include + +#include "vf/Command.h" + +namespace vf { + +namespace { + +const char* const knownFolders[] = {"recipes", "lootTables", "tags/blocks", "tags/items", + "advancements"}; + +std::string loweredWithoutSeparators(const std::string& text) { + std::string out; + out.reserve(text.size()); + for (char c : text) { + if (c == '_' || c == '-') continue; + out.push_back(static_cast(std::tolower(static_cast(c)))); + } + return out; +} + +bool endsWith(const std::string& text, const std::string& suffix) { + return text.size() >= suffix.size() && + text.compare(text.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +bool splitDataPath(const std::string& file, std::string& space, std::string& folder, + std::string& name) { + const std::string root = "data/"; + if (file.compare(0, root.size(), root) != 0) return false; + std::size_t spaceEnd = file.find('/', root.size()); + if (spaceEnd == std::string::npos) return false; + space = file.substr(root.size(), spaceEnd - root.size()); + std::string rest = file.substr(spaceEnd + 1u); + if (!endsWith(rest, ".json")) return false; + rest = rest.substr(0, rest.size() - 5u); + + std::string bestFolder; + for (const char* candidate : knownFolders) { + std::string prefix = std::string(candidate) + "/"; + if (rest.compare(0, prefix.size(), prefix) != 0 || rest.size() <= prefix.size()) continue; + if (bestFolder.size() < prefix.size() - 1u) bestFolder = candidate; + } + if (bestFolder.empty()) return false; + folder = bestFolder; + name = rest.substr(bestFolder.size() + 1u); + return !name.empty(); +} + +bool readResourceField(const JsonValue& owner, const std::string& key, + const std::string& defaultSpace, ResourceId& out, std::string& problem) { + const JsonValue* field = owner.find(key); + if (field == nullptr || !field->isString()) { + problem = "\"" + key + "\" must be a resource id string"; + return false; + } + if (!ResourceId::parse(field->stringValue(), defaultSpace, out)) { + problem = "\"" + key + "\" is not a valid resource id: " + field->stringValue(); + return false; + } + return true; +} + +bool readItemField(const JsonValue& owner, const std::string& key, const std::string& defaultSpace, + ItemId& out, std::string& problem) { + ResourceId id; + if (!readResourceField(owner, key, defaultSpace, id, problem)) return false; + if (!resolveItemId(id, out)) { + problem = "no such item: " + id.text(); + return false; + } + return true; +} + +struct NamedFlag { + const char* name; + uint32_t bit; +}; + +const NamedFlag lootConditionFlags[] = { + {"playerKill", LootEntry::PlayerKill}, + {"fiery", LootEntry::Fiery}, + {"notFiery", LootEntry::NotFiery}, + {"silkTouched", LootEntry::SilkTouched}, + {"notSilkTouched", LootEntry::NotSilkTouched}, + {"needsLooting", LootEntry::NeedsLooting}, + {"needsFortune", LootEntry::NeedsFortune}, + {"explosive", LootEntry::Explosive}, + {"ranged", LootEntry::Ranged}, +}; + +const NamedFlag lootFunctionFlags[] = { + {"countRange", LootEntry::CountRange}, + {"oreFortune", LootEntry::OreFortune}, + {"lootingBonus", LootEntry::LootingBonus}, + {"chanceLooting", LootEntry::ChanceLooting}, + {"chanceFortune", LootEntry::ChanceFortune}, + {"smeltIfFiery", LootEntry::SmeltIfFiery}, +}; + +bool readFlagList(const JsonValue& owner, const std::string& key, const NamedFlag* table, + std::size_t tableSize, uint32_t& 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 names"; + return false; + } + for (const JsonValue& element : list->elements()) { + if (!element.isString()) { + problem = "\"" + key + "\" holds a non string entry"; + return false; + } + bool matched = false; + for (std::size_t i = 0u; i < tableSize; ++i) { + if (element.stringValue() == table[i].name) { + out |= table[i].bit; + matched = true; + break; + } + } + if (!matched) { + problem = "unknown " + key + " name: " + element.stringValue(); + return false; + } + } + return true; +} + +bool readCountRange(const JsonValue& owner, const std::string& key, int& minOut, int& maxOut, + std::string& problem) { + const JsonValue* field = owner.find(key); + if (field == nullptr) return true; + if (field->isNumber()) { + minOut = static_cast(field->intValue()); + maxOut = minOut; + } else if (field->isObject()) { + minOut = static_cast(field->intOr("min", minOut)); + maxOut = static_cast(field->intOr("max", minOut)); + } else { + problem = "\"" + key + "\" must be a number or a {min,max} object"; + return false; + } + if (minOut > maxOut) { + problem = "\"" + key + "\" has min above max"; + return false; + } + if (minOut < 0) { + problem = "\"" + key + "\" cannot be negative"; + return false; + } + return true; +} + +} // namespace +