feat: add expandTag and parseRecipeJson to DataPack
This commit is contained in:
1 parent
4423e6c2b3
commit
7991f6c660
1 file changed
+158
@@ -316,3 +316,161 @@ void TagRegistry::clear() { tags.clear(); }
|
||||
|
||||
bool TagRegistry::has(const ResourceId& id) const { return tags.count(id) > 0u; }
|
||||
|
||||
const BlockTag* TagRegistry::find(const ResourceId& id) const {
|
||||
auto found = tags.find(id);
|
||||
return found == tags.end() ? nullptr : &found->second;
|
||||
}
|
||||
|
||||
std::vector<ResourceId> TagRegistry::ids() const {
|
||||
std::vector<ResourceId> out;
|
||||
out.reserve(tags.size());
|
||||
for (const auto& entry : tags) out.push_back(entry.first);
|
||||
return out;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool expandTag(const TagRegistry& registry, const ResourceId& id, std::vector<ResourceId>& out,
|
||||
std::vector<ResourceId>& path, std::vector<ResourceId>& expanded,
|
||||
std::string& problem) {
|
||||
for (const ResourceId& onPath : path) {
|
||||
if (onPath == id) {
|
||||
problem = "tag cycle through " + id.text();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const ResourceId& done : expanded)
|
||||
if (done == id) return true;
|
||||
|
||||
const BlockTag* tag = registry.find(id);
|
||||
if (tag == nullptr) {
|
||||
problem = "unknown tag " + id.text();
|
||||
return false;
|
||||
}
|
||||
path.push_back(id);
|
||||
for (const BlockTag::Entry& entry : tag->entries) {
|
||||
if (entry.reference) {
|
||||
if (!expandTag(registry, entry.id, out, path, expanded, problem)) return false;
|
||||
continue;
|
||||
}
|
||||
bool already = false;
|
||||
for (const ResourceId& present : out)
|
||||
if (present == entry.id) already = true;
|
||||
if (!already) out.push_back(entry.id);
|
||||
}
|
||||
path.pop_back();
|
||||
expanded.push_back(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TagRegistry::resolve(const ResourceId& id, std::vector<ResourceId>& out,
|
||||
std::string& problem) const {
|
||||
out.clear();
|
||||
std::vector<ResourceId> path;
|
||||
std::vector<ResourceId> expanded;
|
||||
if (!expandTag(*this, id, out, path, expanded, problem)) {
|
||||
out.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TagRegistry::resolve(const ResourceId& id, std::vector<ResourceId>& out) const {
|
||||
std::string ignored;
|
||||
return resolve(id, out, ignored);
|
||||
}
|
||||
|
||||
bool TagRegistry::contains(const ResourceId& id, const ResourceId& member) const {
|
||||
std::vector<ResourceId> members;
|
||||
if (!resolve(id, members)) return false;
|
||||
for (const ResourceId& present : members)
|
||||
if (present == member) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TagRegistry::hasCycle(const ResourceId& id) const {
|
||||
std::vector<ResourceId> members;
|
||||
std::string problem;
|
||||
if (resolve(id, members, problem)) return false;
|
||||
return problem.compare(0, 10, "tag cycle ") == 0;
|
||||
}
|
||||
|
||||
bool parseRecipeJson(const JsonValue& root, const std::string& defaultSpace, RecipeData& out,
|
||||
std::string& problem) {
|
||||
if (!root.isObject()) {
|
||||
problem = "a recipe must be an object";
|
||||
return false;
|
||||
}
|
||||
const JsonValue* type = root.find("type");
|
||||
if (type == nullptr || !type->isString()) {
|
||||
problem = "\"type\" must be one of craftingShaped, craftingShapeless, smelting";
|
||||
return false;
|
||||
}
|
||||
const JsonValue* result = root.find("result");
|
||||
if (result == nullptr || !result->isObject()) {
|
||||
problem = "\"result\" must be an object";
|
||||
return false;
|
||||
}
|
||||
RecipeData parsed;
|
||||
if (!readItemField(*result, "item", defaultSpace, parsed.resultItem, problem)) return false;
|
||||
parsed.resultCount = static_cast<int>(result->intOr("count", 1));
|
||||
if (parsed.resultCount < 1 || parsed.resultCount > maxStackSize(parsed.resultItem)) {
|
||||
problem = "result count out of range for " + result->stringOr("item", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& kindName = type->stringValue();
|
||||
if (kindName == "craftingShaped") {
|
||||
parsed.kind = RecipeData::Kind::Shaped;
|
||||
const JsonValue* pattern = root.find("pattern");
|
||||
const JsonValue* key = root.find("key");
|
||||
if (pattern == nullptr || !pattern->isArray() || pattern->elements().empty()) {
|
||||
problem = "\"pattern\" must be a non empty array of rows";
|
||||
return false;
|
||||
}
|
||||
if (key == nullptr || !key->isObject()) {
|
||||
problem = "\"key\" must be an object mapping pattern characters to items";
|
||||
return false;
|
||||
}
|
||||
if (pattern->elements().size() > static_cast<std::size_t>(craftGridHeight)) {
|
||||
problem = "\"pattern\" is taller than the crafting grid";
|
||||
return false;
|
||||
}
|
||||
parsed.height = static_cast<int>(pattern->elements().size());
|
||||
parsed.width = -1;
|
||||
parsed.pattern.fill(ItemId::None);
|
||||
for (int row = 0; row < parsed.height; ++row) {
|
||||
const JsonValue& line = pattern->elements()[static_cast<std::size_t>(row)];
|
||||
if (!line.isString()) {
|
||||
problem = "\"pattern\" rows must be strings";
|
||||
return false;
|
||||
}
|
||||
const std::string& cells = line.stringValue();
|
||||
if (cells.empty() || cells.size() > static_cast<std::size_t>(craftGridWidth)) {
|
||||
problem = "\"pattern\" row width must be 1 to 3 characters";
|
||||
return false;
|
||||
}
|
||||
if (parsed.width < 0) parsed.width = static_cast<int>(cells.size());
|
||||
if (parsed.width != static_cast<int>(cells.size())) {
|
||||
problem = "\"pattern\" rows are not all the same width";
|
||||
return false;
|
||||
}
|
||||
for (int column = 0; column < parsed.width; ++column) {
|
||||
char symbol = cells[static_cast<std::size_t>(column)];
|
||||
if (symbol == ' ') continue;
|
||||
ItemId ingredient = ItemId::None;
|
||||
if (!readItemField(*key, std::string(1, symbol), defaultSpace, ingredient,
|
||||
problem)) {
|
||||
problem = "pattern character '" + std::string(1, symbol) + "': " + problem;
|
||||
return false;
|
||||
}
|
||||
// Packed by the recipe's own width, which is how matchesShaped
|
||||
// in Crafting.cpp indexes a pattern.
|
||||
parsed.pattern[static_cast<std::size_t>(row * parsed.width + column)] = ingredient;
|
||||
}
|
||||
}
|
||||
parsed.allowMirror = root.boolOr("mirror", true);
|
||||
} else if (kindName == "craftingShapeless") {
|
||||
parsed.kind = RecipeData::Kind::Shapeless;
|
||||
Reference in new issue
Block a user