diff --git a/src/content/ContentBuilder.hpp b/src/content/ContentBuilder.hpp index 22610eee..a0daa9ac 100644 --- a/src/content/ContentBuilder.hpp +++ b/src/content/ContentBuilder.hpp @@ -45,6 +45,14 @@ public: defs[id] = std::make_unique(id); return *defs[id]; } + // Only fetch existing definition, return null otherwise. + T* get(const std::string& id) { + auto found = defs.find(id); + if (found != defs.end()) { + return &*found->second; + } + return nullptr; + } auto build() { return std::move(defs); diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index c08e1c43..bc8a5f3b 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -6,6 +6,9 @@ #include #include +#include "Content.hpp" +#include "ContentBuilder.hpp" +#include "ContentPack.hpp" #include "coders/json.hpp" #include "core_defs.hpp" #include "data/dynamic.hpp" @@ -18,9 +21,6 @@ #include "util/listutil.hpp" #include "util/stringutil.hpp" #include "voxels/Block.hpp" -#include "Content.hpp" -#include "ContentBuilder.hpp" -#include "ContentPack.hpp" namespace fs = std::filesystem; @@ -123,6 +123,18 @@ void ContentLoader::loadBlock( ) { auto root = files::read_json(file); + if (root->has("parent")) { + std::string parentName; + root->str("parent", parentName); + auto parentDef = this->builder.blocks.get(parentName); + if (parentDef == nullptr) { + throw std::runtime_error( + "Failed to find parent(" + parentName + ") for " + name + ); + } + parentDef->cloneTo(def); + } + root->str("caption", def.caption); // block texturing @@ -289,6 +301,19 @@ void ContentLoader::loadItem( ItemDef& def, const std::string& name, const fs::path& file ) { auto root = files::read_json(file); + + if (root->has("parent")) { + std::string parentName; + root->str("parent", parentName); + auto parentDef = this->builder.items.get(parentName); + if (parentDef == nullptr) { + throw std::runtime_error( + "Failed to find parent(" + parentName + ") for " + name + ); + } + parentDef->cloneTo(def); + } + root->str("caption", def.caption); std::string iconTypeStr = ""; @@ -319,6 +344,19 @@ void ContentLoader::loadEntity( EntityDef& def, const std::string& name, const fs::path& file ) { auto root = files::read_json(file); + + if (root->has("parent")) { + std::string parentName; + root->str("parent", parentName); + auto parentDef = this->builder.entities.get(parentName); + if (parentDef == nullptr) { + throw std::runtime_error( + "Failed to find parent(" + parentName + ") for " + name + ); + } + parentDef->cloneTo(def); + } + if (auto componentsarr = root->list("components")) { for (size_t i = 0; i < componentsarr->size(); i++) { def.components.emplace_back(componentsarr->str(i)); @@ -340,7 +378,8 @@ void ContentLoader::loadEntity( sensorarr->num(3)}, {sensorarr->num(4), sensorarr->num(5), - sensorarr->num(6)}} + sensorarr->num(6)} + } ); } else if (sensorType == "radius") { def.radialSensors.emplace_back(i, sensorarr->num(1)); @@ -484,45 +523,157 @@ void ContentLoader::load() { if (!fs::is_regular_file(pack->getContentFile())) return; auto root = files::read_json(pack->getContentFile()); + std::vector> pendingDefs; + auto getJsonParent = [this](const std::string& prefix, const std::string& name) { + auto configFile = pack->folder / fs::path(prefix + "/" + name + ".json"); + std::string parent; + if (fs::exists(configFile)) { + auto root = files::read_json(configFile); + if (root->has("parent")) root->str("parent", parent); + } + return parent; + }; + auto processName = [this](const std::string& name) { + auto colon = name.find(':'); + auto new_name = name; + std::string full = + colon == std::string::npos ? pack->id + ":" + name : name; + if (colon != std::string::npos) new_name[colon] = '/'; + + return std::make_pair(full, new_name); + }; if (auto blocksarr = root->list("blocks")) { for (size_t i = 0; i < blocksarr->size(); i++) { - std::string name = blocksarr->str(i); - auto [packid, full, filename] = create_unit_id(pack->id, name); - - auto& def = builder.blocks.create(full); - if (filename != name) { - def.scriptName = packid + "/" + def.scriptName; + auto [full, name] = processName(blocksarr->str(i)); + auto parent = getJsonParent("blocks", name); + if (parent.empty() || builder.blocks.get(parent)) { + // No dependency or dependency already loaded/exists in another + // content pack + auto& def = builder.blocks.create(full); + loadBlock(def, full, name); + stats->totalBlocks++; + } else { + // Dependency not loaded yet, add to pending items + pendingDefs.emplace_back(full, name); } + } - loadBlock(def, full, filename); - stats->totalBlocks++; + // Resolve dependencies for pending items + bool progressMade = true; + while (!pendingDefs.empty() && progressMade) { + progressMade = false; + + for (auto it = pendingDefs.begin(); it != pendingDefs.end();) { + auto parent = getJsonParent("blocks", it->second); + if (builder.blocks.get(parent)) { + // Dependency resolved or parent exists in another pack, + // load the item + auto& def = builder.blocks.create(it->first); + loadBlock(def, it->first, it->second); + stats->totalBlocks++; + it = pendingDefs.erase(it); // Remove resolved item + progressMade = true; + } else { + ++it; + } + } + } + + if (!pendingDefs.empty()) { + // Handle circular dependencies or missing dependencies + // You can log an error or throw an exception here if necessary + throw std::runtime_error("Unresolved block dependencies detected."); } } + if (auto itemsarr = root->list("items")) { for (size_t i = 0; i < itemsarr->size(); i++) { - std::string name = itemsarr->str(i); - auto [packid, full, filename] = create_unit_id(pack->id, name); - - auto& def = builder.items.create(full); - if (filename != name) { - def.scriptName = packid + "/" + def.scriptName; + auto [full, name] = processName(itemsarr->str(i)); + auto parent = getJsonParent("items", name); + if (parent.empty() || builder.items.get(parent)) { + // No dependency or dependency already loaded/exists in another + // content pack + auto& def = builder.items.create(full); + loadItem(def, full, name); + stats->totalItems++; + } else { + // Dependency not loaded yet, add to pending items + pendingDefs.emplace_back(full, name); } + } - loadItem(def, full, filename); - stats->totalItems++; + // Resolve dependencies for pending items + bool progressMade = true; + while (!pendingDefs.empty() && progressMade) { + progressMade = false; + + for (auto it = pendingDefs.begin(); it != pendingDefs.end();) { + auto parent = getJsonParent("items", it->second); + if (builder.items.get(parent)) { + // Dependency resolved or parent exists in another pack, + // load the item + auto& def = builder.items.create(it->first); + loadItem(def, it->first, it->second); + stats->totalItems++; + it = pendingDefs.erase(it); // Remove resolved item + progressMade = true; + } else { + ++it; + } + } + } + + if (!pendingDefs.empty()) { + // Handle circular dependencies or missing dependencies + // You can log an error or throw an exception here if necessary + throw std::runtime_error("Unresolved item dependencies detected."); } } if (auto entitiesarr = root->list("entities")) { for (size_t i = 0; i < entitiesarr->size(); i++) { - std::string name = entitiesarr->str(i); - auto [packid, full, filename] = create_unit_id(pack->id, name); + auto [full, name] = processName(entitiesarr->str(i)); + auto parent = getJsonParent("entities", name); + if (parent.empty() || builder.entities.get(parent)) { + // No dependency or dependency already loaded/exists in another + // content pack + auto& def = builder.entities.create(full); + loadEntity(def, full, name); + stats->totalEntities++; + } else { + // Dependency not loaded yet, add to pending items + pendingDefs.emplace_back(full, name); + } + } - auto& def = builder.entities.create(full); + // Resolve dependencies for pending items + bool progressMade = true; + while (!pendingDefs.empty() && progressMade) { + progressMade = false; - loadEntity(def, full, name); - stats->totalEntities++; + for (auto it = pendingDefs.begin(); it != pendingDefs.end();) { + auto parent = getJsonParent("entities", it->second); + if (builder.entities.get(parent)) { + // Dependency resolved or parent exists in another pack, + // load the item + auto& def = builder.entities.create(it->first); + loadEntity(def, it->first, it->second); + stats->totalEntities++; + it = pendingDefs.erase(it); // Remove resolved item + progressMade = true; + } else { + ++it; + } + } + } + + if (!pendingDefs.empty()) { + // Handle circular dependencies or missing dependencies + // You can log an error or throw an exception here if necessary + throw std::runtime_error( + "Unresolved entities dependencies detected." + ); } } diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index 0a65f640..01cebae5 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -46,13 +46,13 @@ class ContentLoader { static void loadCustomBlockModel(Block& def, dynamic::Map* primitives); static void loadBlockMaterial(BlockMaterial& def, const fs::path& file); - static void loadBlock( + void loadBlock( Block& def, const std::string& name, const fs::path& file ); - static void loadItem( + void loadItem( ItemDef& def, const std::string& name, const fs::path& file ); - static void loadEntity( + void loadEntity( EntityDef& def, const std::string& name, const fs::path& file ); void loadResources(ResourceType type, dynamic::List* list); diff --git a/src/items/ItemDef.cpp b/src/items/ItemDef.cpp index 539682c6..321f7be7 100644 --- a/src/items/ItemDef.cpp +++ b/src/items/ItemDef.cpp @@ -5,3 +5,13 @@ ItemDef::ItemDef(const std::string& name) : name(name) { caption = util::id_to_caption(name); } +void ItemDef::cloneTo(ItemDef& dst) { + dst.caption = caption; + dst.stackSize = stackSize; + dst.generated = generated; + std::copy(&emission[0], &emission[3], dst.emission); + dst.iconType = iconType; + dst.icon = icon; + dst.placingBlock = placingBlock; + dst.scriptName = scriptName; +} diff --git a/src/items/ItemDef.hpp b/src/items/ItemDef.hpp index 348ff844..a306dce2 100644 --- a/src/items/ItemDef.hpp +++ b/src/items/ItemDef.hpp @@ -44,4 +44,5 @@ struct ItemDef { ItemDef(const std::string& name); ItemDef(const ItemDef&) = delete; + void cloneTo(ItemDef& dst); }; diff --git a/src/objects/EntityDef.cpp b/src/objects/EntityDef.cpp new file mode 100644 index 00000000..8fa9c129 --- /dev/null +++ b/src/objects/EntityDef.cpp @@ -0,0 +1,12 @@ +#include "EntityDef.hpp" +void EntityDef::cloneTo(EntityDef& dst) { + dst.components = components; + dst.bodyType = bodyType; + dst.hitbox = hitbox; + dst.boxSensors = boxSensors; + dst.radialSensors = radialSensors; + dst.skeletonName = skeletonName; + dst.blocking = blocking; + dst.save = save; + +} \ No newline at end of file diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index 021c67f1..dec61730 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -24,12 +24,12 @@ struct EntityDef { /// @brief Hitbox size glm::vec3 hitbox {0.25f}; - + /// @brief 'aabb' sensors std::vector> boxSensors {}; /// @brief 'radius' sensors std::vector> radialSensors {}; - + /// @brief Skeleton ID std::string skeletonName = name; @@ -56,4 +56,6 @@ struct EntityDef { EntityDef(const std::string& name) : name(name) {} EntityDef(const EntityDef&) = delete; + void cloneTo(EntityDef& dst); }; + diff --git a/src/voxels/Block.cpp b/src/voxels/Block.cpp index aee744a1..ec125430 100644 --- a/src/voxels/Block.cpp +++ b/src/voxels/Block.cpp @@ -64,7 +64,8 @@ const BlockRotProfile BlockRotProfile::NONE { {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, // West {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, // Up {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, // Down - }}; + } +}; const BlockRotProfile BlockRotProfile::PIPE { "pipe", @@ -75,7 +76,8 @@ const BlockRotProfile BlockRotProfile::PIPE { {{0, 0, -1}, {1, 0, 0}, {0, -1, 0}}, // West {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, // Up {{1, 0, 0}, {0, -1, 0}, {0, 0, -1}}, // Down - }}; + } +}; const BlockRotProfile BlockRotProfile::PANE { "pane", @@ -84,7 +86,8 @@ const BlockRotProfile BlockRotProfile::PANE { {{0, 0, -1}, {0, 1, 0}, {1, 0, 0}}, // East {{-1, 0, 0}, {0, 1, 0}, {0, 0, -1}}, // South {{0, 0, 1}, {0, 1, 0}, {-1, 0, 0}}, // West - }}; + } +}; Block::Block(const std::string& name) : name(name), @@ -95,10 +98,43 @@ Block::Block(const std::string& name) TEXTURE_NOTFOUND, TEXTURE_NOTFOUND, TEXTURE_NOTFOUND, - TEXTURE_NOTFOUND} { + TEXTURE_NOTFOUND + } { } Block::Block(std::string name, const std::string& texture) : name(std::move(name)), textureFaces {texture, texture, texture, texture, texture, texture} { } +void Block::cloneTo(Block& dst) { + dst.caption = caption; + for (int i = 0; i < 6; i++) { + dst.textureFaces[i] = textureFaces[i]; + } + dst.modelTextures = modelTextures; + dst.modelBoxes = modelBoxes; + dst.modelExtraPoints = modelExtraPoints; + dst.modelUVs = modelUVs; + dst.material = material; + std::copy(&emission[0], &emission[3], dst.emission); + dst.size = size; + dst.model = model; + dst.lightPassing = lightPassing; + dst.skyLightPassing = skyLightPassing; + dst.shadeless = shadeless; + dst.ambientOcclusion = ambientOcclusion; + dst.obstacle = obstacle; + dst.selectable = selectable; + dst.replaceable = replaceable; + dst.breakable = breakable; + dst.rotatable = rotatable; + dst.grounded = grounded; + dst.hidden = hidden; + dst.hitboxes = hitboxes; + dst.rotations = rotations; + dst.pickingItem = pickingItem; + dst.scriptName = scriptName; + dst.uiLayout = uiLayout; + dst.inventorySize = inventorySize; + dst.tickInterval = tickInterval; +} diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index a32ddd1c..210972bb 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -211,6 +211,8 @@ public: Block(const std::string& name); Block(std::string name, const std::string& texture); Block(const Block&) = delete; + + void cloneTo(Block& dst); }; inline glm::ivec3 get_ground_direction(const Block& def, int rotation) {