From 4423e6c2b3f527df0f0dda6f89f1c411b89aa012 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 7 Sep 2026 11:17:59 +0200 Subject: [PATCH] feat: add resolveItemId and resolveBlockId to DataPack --- core/src/DataPack.cpp | 159 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/core/src/DataPack.cpp b/core/src/DataPack.cpp index cfcfd3e..25bf783 100644 --- a/core/src/DataPack.cpp +++ b/core/src/DataPack.cpp @@ -157,3 +157,162 @@ bool readCountRange(const JsonValue& owner, const std::string& key, int& minOut, } // namespace +ResourceId::ResourceId(std::string space, std::string path) + : space(std::move(space)), path(std::move(path)) {} + +bool ResourceId::validSpace(const std::string& text) { + if (text.empty()) return false; + for (char c : text) { + bool ok = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || + c == '.'; + if (!ok) return false; + } + return true; +} + +bool ResourceId::validPath(const std::string& text) { + if (text.empty()) return false; + if (text.front() == '/' || text.back() == '/') return false; + for (char c : text) { + bool ok = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || + c == '.' || c == '/'; + if (!ok) return false; + } + return text.find("//") == std::string::npos; +} + +bool ResourceId::parse(const std::string& text, const std::string& defaultSpace, ResourceId& out) { + std::size_t colon = text.find(':'); + if (colon == std::string::npos) { + out = ResourceId(defaultSpace, text); + } else { + if (text.find(':', colon + 1u) != std::string::npos) return false; + out = ResourceId(text.substr(0, colon), text.substr(colon + 1u)); + } + return out.valid(); +} + +bool resolveItemId(const ResourceId& id, ItemId& out) { + if (!id.valid()) return false; + return parseItemName(loweredWithoutSeparators(id.path), out); +} + +bool resolveBlockId(const ResourceId& id, Block& out) { + if (!id.valid()) return false; + return parseBlockName(loweredWithoutSeparators(id.path), out); +} + +bool DataPackMeta::fromJson(const JsonValue& root, DataPackMeta& out, std::string& problem) { + if (!root.isObject()) { + problem = "pack metadata must be an object"; + return false; + } + const JsonValue* name = root.find("name"); + if (name == nullptr || !name->isString() || name->stringValue().empty()) { + problem = "\"name\" must be a non empty string"; + return false; + } + DataPackMeta parsed; + parsed.name = name->stringValue(); + parsed.description = root.stringOr("description", ""); + parsed.formatVersion = static_cast(root.intOr("formatVersion", 1)); + parsed.priority = static_cast(root.intOr("priority", 0)); + parsed.defaultSpace = root.stringOr("defaultSpace", "voxelforge"); + if (parsed.formatVersion < 1) { + problem = "\"formatVersion\" must be at least 1"; + return false; + } + if (!ResourceId::validSpace(parsed.defaultSpace)) { + problem = "\"defaultSpace\" is not a valid namespace: " + parsed.defaultSpace; + return false; + } + out = parsed; + return true; +} + +std::string DataPackError::text() const { + std::string out = pack.empty() ? file : pack + "/" + file; + if (line > 0) out += ":" + std::to_string(line) + ":" + std::to_string(column); + return out + ": " + reason; +} + +void DataPack::addFile(const std::string& path, std::string contents) { + files[path] = std::move(contents); +} + +bool DataPack::removeFile(const std::string& path) { return files.erase(path) > 0u; } + +bool DataPack::hasFile(const std::string& path) const { return files.count(path) > 0u; } + +const std::string* DataPack::fileContents(const std::string& path) const { + auto found = files.find(path); + return found == files.end() ? nullptr : &found->second; +} + +std::vector DataPack::filesUnder(const std::string& prefix) const { + std::vector out; + for (const auto& entry : files) + if (entry.first.compare(0, prefix.size(), prefix) == 0) out.push_back(entry.first); + return out; +} + +std::vector DataPack::allFiles() const { + std::vector out; + out.reserve(files.size()); + for (const auto& entry : files) out.push_back(entry.first); + return out; +} + +ShapedRecipe RecipeData::toShaped() const { + ShapedRecipe out; + out.width = width; + out.height = height; + out.pattern = pattern; + out.allowMirror = allowMirror; + out.result.item = resultItem; + out.result.count = resultCount; + return out; +} + +ShapelessRecipe RecipeData::toShapeless() const { + ShapelessRecipe out; + out.ingredients = ingredients; + out.result.item = resultItem; + out.result.count = resultCount; + return out; +} + +SmeltingRecipe RecipeData::toSmelting() const { + SmeltingRecipe out; + out.input = smeltInput; + out.smeltSeconds = smeltSeconds; + out.result.item = resultItem; + out.result.count = resultCount; + return out; +} + +bool BlockTag::contains(const ResourceId& member) const { + for (const Entry& entry : entries) + if (!entry.reference && entry.id == member) return true; + return false; +} + +void TagRegistry::define(const BlockTag& tag) { + auto found = tags.find(tag.id); + if (found == tags.end() || tag.replace) { + tags[tag.id] = tag; + return; + } + BlockTag& existing = found->second; + for (const BlockTag::Entry& entry : tag.entries) { + bool already = false; + for (const BlockTag::Entry& present : existing.entries) + if (present == entry) already = true; + if (!already) existing.entries.push_back(entry); + } +} + +void TagRegistry::clear() { tags.clear(); } + +bool TagRegistry::has(const ResourceId& id) const { return tags.count(id) > 0u; } +