feat: add parseLootTableJson and parseTagJson to DataPack

This commit is contained in:
dualfroz committed 2026-09-07 13:20:04 +02:00
1 parent 7991f6c660
commit c2493cb50a
1 file changed
+160
+160
View File
@@ -474,3 +474,163 @@ bool parseRecipeJson(const JsonValue& root, const std::string& defaultSpace, Rec
parsed.allowMirror = root.boolOr("mirror", true); parsed.allowMirror = root.boolOr("mirror", true);
} else if (kindName == "craftingShapeless") { } else if (kindName == "craftingShapeless") {
parsed.kind = RecipeData::Kind::Shapeless; parsed.kind = RecipeData::Kind::Shapeless;
const JsonValue* ingredients = root.find("ingredients");
if (ingredients == nullptr || !ingredients->isArray() || ingredients->elements().empty()) {
problem = "\"ingredients\" must be a non empty array";
return false;
}
if (ingredients->elements().size() > static_cast<std::size_t>(craftGridCells)) {
problem = "\"ingredients\" holds more than the grid can take";
return false;
}
for (const JsonValue& element : ingredients->elements()) {
if (!element.isString()) {
problem = "\"ingredients\" holds a non string entry";
return false;
}
ResourceId id;
ItemId ingredient = ItemId::None;
if (!ResourceId::parse(element.stringValue(), defaultSpace, id) ||
!resolveItemId(id, ingredient)) {
problem = "no such item: " + element.stringValue();
return false;
}
parsed.ingredients.push_back(ingredient);
}
} else if (kindName == "smelting") {
parsed.kind = RecipeData::Kind::Smelting;
if (!readItemField(root, "ingredient", defaultSpace, parsed.smeltInput, problem))
return false;
parsed.smeltSeconds = static_cast<float>(root.numberOr("seconds", 10.0));
parsed.experience = static_cast<float>(root.numberOr("experience", 0.0));
if (parsed.smeltSeconds <= 0.0f) {
problem = "\"seconds\" must be above zero";
return false;
}
if (parsed.experience < 0.0f) {
problem = "\"experience\" cannot be negative";
return false;
}
} else {
problem = "unknown recipe type: " + kindName;
return false;
}
out = parsed;
return true;
}
bool parseLootTableJson(const JsonValue& root, const std::string& defaultSpace, LootTable& out,
std::string& problem) {
if (!root.isObject()) {
problem = "a loot table must be an object";
return false;
}
const JsonValue* pools = root.find("pools");
if (pools == nullptr || !pools->isArray() || pools->elements().empty()) {
problem = "\"pools\" must be a non empty array";
return false;
}
LootTable parsed;
for (const JsonValue& poolJson : pools->elements()) {
if (!poolJson.isObject()) {
problem = "every pool must be an object";
return false;
}
LootPool pool;
if (!readCountRange(poolJson, "rolls", pool.minRolls, pool.maxRolls, problem)) return false;
pool.bonusRollsPerLooting = static_cast<int>(poolJson.intOr("bonusRollsPerLooting", 0));
if (pool.bonusRollsPerLooting < 0) {
problem = "\"bonusRollsPerLooting\" cannot be negative";
return false;
}
const JsonValue* entries = poolJson.find("entries");
if (entries == nullptr || !entries->isArray() || entries->elements().empty()) {
problem = "every pool needs a non empty \"entries\" array";
return false;
}
for (const JsonValue& entryJson : entries->elements()) {
if (!entryJson.isObject()) {
problem = "every loot entry must be an object";
return false;
}
LootEntry entry;
if (!readItemField(entryJson, "item", defaultSpace, entry.item, problem)) return false;
entry.weight = static_cast<int>(entryJson.intOr("weight", 1));
if (entry.weight < 1) {
problem = "\"weight\" must be at least 1";
return false;
}
if (!readCountRange(entryJson, "count", entry.minCount, entry.maxCount, problem))
return false;
// An authored range is meant to be rolled; the roller only does that
// when the CountRange function is on the entry.
if (entry.maxCount > entry.minCount) entry.functions |= LootEntry::CountRange;
entry.chance = static_cast<float>(entryJson.numberOr("chance", 1.0));
entry.chancePerLevel = static_cast<float>(entryJson.numberOr("chancePerLevel", 0.0));
if (entry.chance < 0.0f || entry.chance > 1.0f) {
problem = "\"chance\" must sit between 0 and 1";
return false;
}
if (!readFlagList(entryJson, "conditions", lootConditionFlags,
sizeof(lootConditionFlags) / sizeof(lootConditionFlags[0]),
entry.conditions, problem))
return false;
if (!readFlagList(entryJson, "functions", lootFunctionFlags,
sizeof(lootFunctionFlags) / sizeof(lootFunctionFlags[0]),
entry.functions, problem))
return false;
pool.entries.push_back(entry);
}
parsed.pools.push_back(pool);
}
out = parsed;
return true;
}
bool parseTagJson(const JsonValue& root, const std::string& defaultSpace, BlockTag& out,
std::string& problem) {
if (!root.isObject()) {
problem = "a tag must be an object";
return false;
}
const JsonValue* values = root.find("values");
if (values == nullptr || !values->isArray()) {
problem = "\"values\" must be an array";
return false;
}
BlockTag parsed;
parsed.replace = root.boolOr("replace", false);
for (const JsonValue& element : values->elements()) {
if (!element.isString()) {
problem = "\"values\" holds a non string entry";
return false;
}
BlockTag::Entry entry;
std::string text = element.stringValue();
if (!text.empty() && text[0] == '#') {
entry.reference = true;
text = text.substr(1);
}
if (!ResourceId::parse(text, defaultSpace, entry.id)) {
problem = "not a valid resource id: " + element.stringValue();
return false;
}
parsed.entries.push_back(entry);
}
out = parsed;
return true;
}
void DataPackLoader::addPack(DataPack pack) { loaded.push_back(std::move(pack)); }
void DataPackLoader::clear() {
loaded.clear();
problems.clear();
order.clear();
folders.clear();
recipeSet.clear();
lootSet.clear();
blockTagSet.clear();
itemTagSet.clear();
}