diff --git a/AUTHORS.h b/AUTHORS.h index a9ff41e66..fb6552959 100644 --- a/AUTHORS.h +++ b/AUTHORS.h @@ -46,6 +46,7 @@ const std::vector> contributors = { { "Developing", "", "vmarkovtsev", "" }, { "Developing", "Tom Zielinski", "Warmonger", "Warmonger@vp.pl" }, { "Developing", "Xiaomin Ding", "", "dingding303@gmail.com" }, + { "Developing", "Fenghuang Rumeng", "kdmcser", "zqtndfj@gmail.com" }, { "Testing", "Ben Yan", "by003", "benyan9110@gmail.com," }, { "Testing", "", "Misiokles", "" }, diff --git a/Mods/vcmi/config/vcmi/chinese.json b/Mods/vcmi/config/vcmi/chinese.json index 96adec1ed..a38a03669 100644 --- a/Mods/vcmi/config/vcmi/chinese.json +++ b/Mods/vcmi/config/vcmi/chinese.json @@ -175,6 +175,7 @@ "vcmi.server.errors.modsToEnable" : "{需要启用的mod列表}", "vcmi.server.errors.modsToDisable" : "{需要禁用的mod列表}", "vcmi.server.errors.modNoDependency" : "读取mod包 {'%s'}失败!\n 需要的mod {'%s'} 没有安装或无效!\n", + "vcmi.server.errors.modDependencyLoop" : "读取mod包 {'%s'}失败!\n 这个mod可能存在循环(软)依赖!", "vcmi.server.errors.modConflict" : "读取的mod包 {'%s'}无法运行!\n 与另一个mod {'%s'}冲突!\n", "vcmi.server.errors.unknownEntity" : "加载保存失败! 在保存的游戏中发现未知实体'%s'! 保存可能与当前安装的mod版本不兼容!", diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index b6ec4f1ae..19a621c82 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -175,6 +175,7 @@ "vcmi.server.errors.modsToEnable" : "{Following mods are required}", "vcmi.server.errors.modsToDisable" : "{Following mods must be disabled}", "vcmi.server.errors.modNoDependency" : "Failed to load mod {'%s'}!\n It depends on mod {'%s'} which is not active!\n", + "vcmi.server.errors.modDependencyLoop" : "Failed to load mod {'%s'}!\n It maybe in a (soft) dependency loop.", "vcmi.server.errors.modConflict" : "Failed to load mod {'%s'}!\n Conflicts with active mod {'%s'}!\n", "vcmi.server.errors.unknownEntity" : "Failed to load save! Unknown entity '%s' found in saved game! Save may not be compatible with currently installed version of mods!", diff --git a/config/schemas/mod.json b/config/schemas/mod.json index 76d174c28..d98cb4ab3 100644 --- a/config/schemas/mod.json +++ b/config/schemas/mod.json @@ -122,6 +122,11 @@ "description" : "List of mods that are required to run this one", "items" : { "type" : "string" } }, + "softDepends" : { + "type" : "array", + "description" : "List of mods if they are enabled, should be loaded before this one. This mod will overwrite any conflicting items from its soft dependency mods", + "items" : { "type" : "string" } + }, "conflicts" : { "type" : "array", "description" : "List of mods that can't be enabled in the same time as this one", diff --git a/docs/modders/Mod_File_Format.md b/docs/modders/Mod_File_Format.md index fd4354fa9..3379ba7f2 100644 --- a/docs/modders/Mod_File_Format.md +++ b/docs/modders/Mod_File_Format.md @@ -48,6 +48,12 @@ [ "baseMod" ], + + // List of mods if they are enabled, should be loaded before this one. This mod will overwrite any conflicting items from its soft dependency mods. + "softDepends" : + [ + "baseMod" + ], // List of mods that can't be enabled in the same time as this one "conflicts" : diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 5afd59111..3f9f5078a 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -87,8 +87,9 @@ std::vector CModHandler::validateAndSortDependencies(std::vector sortedValidMods; // Vector keeps order of elements (LIFO) sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements + std::set notResolvedModIDs(modsToResolve.begin(), modsToResolve.end()); // Use a set for validation for performance reason - // Mod is resolved if it has not dependencies or all its dependencies are already resolved + // Mod is resolved if it has no dependencies or all its dependencies are already resolved auto isResolved = [&](const CModInfo & mod) -> bool { if(mod.dependencies.size() > resolvedModIDs.size()) @@ -100,6 +101,12 @@ std::vector CModHandler::validateAndSortDependencies(std::vector CModHandler::validateAndSortDependencies(std::vector CModHandler::validateAndSortDependencies(std::vector appendTextID("vcmi.server.errors.modDependencyLoop"); + if (allMods.count(brokenModID)) + modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); + else + modLoadErrors->replaceRawString(brokenModID); + } + } return sortedValidMods; } @@ -352,6 +382,9 @@ void CModHandler::loadModFilesystems() if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) continue; + if (getModSoftDependencies(leftModName).count(rightModName) || getModSoftDependencies(rightModName).count(leftModName)) + continue; + const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY;}; std::unordered_set leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); @@ -417,6 +450,28 @@ std::set CModHandler::getModDependencies(const TModID & modId, bool & is return {}; } +std::set CModHandler::getModSoftDependencies(const TModID & modId) const +{ + auto it = allMods.find(modId); + if(it != allMods.end()) + return it->second.softDependencies; + logMod->error("Mod not found: '%s'", modId); + return {}; +} + +std::set CModHandler::getModEnabledSoftDependencies(const TModID & modId) const +{ + std::set softDependencies = getModSoftDependencies(modId); + for (auto it = softDependencies.begin(); it != softDependencies.end();) + { + if (allMods.find(*it) == allMods.end()) + it = softDependencies.erase(it); + else + it++; + } + return softDependencies; +} + void CModHandler::initializeConfig() { VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"])); diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index fab2d319b..0f305792f 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -66,6 +66,8 @@ public: std::set getModDependencies(const TModID & modId) const; std::set getModDependencies(const TModID & modId, bool & isModFound) const; + std::set getModSoftDependencies(const TModID & modId) const; + std::set getModEnabledSoftDependencies(const TModID & modId) const; /// returns list of all (active) mods std::vector getAllMods() const; diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp index 66e7421c7..fb0778f6f 100644 --- a/lib/modding/CModInfo.cpp +++ b/lib/modding/CModInfo.cpp @@ -43,6 +43,7 @@ CModInfo::CModInfo(): CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config): identifier(identifier), dependencies(readModList(config["depends"])), + softDependencies(readModList(config["softDepends"])), conflicts(readModList(config["conflicts"])), explicitlyEnabled(false), implicitlyEnabled(true), diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h index 3d6b40320..d5c077167 100644 --- a/lib/modding/CModInfo.h +++ b/lib/modding/CModInfo.h @@ -44,6 +44,9 @@ public: /// list of mods that should be loaded before this one std::set dependencies; + /// list of mods if they are enabled, should be loaded before this one. this mod will overwrite any conflicting items from its soft dependency mods + std::set softDependencies; + /// list of mods that can't be used in the same time as this one std::set conflicts; diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index c5f85add0..2696080fe 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -202,7 +202,10 @@ void ContentTypeHandler::afterLoadFinalization() conflictingMods.insert(conflictModEntry.first); for (auto const & modID : conflictingMods) + { resolvedConflicts.merge(VLC->modh->getModDependencies(modID)); + resolvedConflicts.merge(VLC->modh->getModEnabledSoftDependencies(modID)); + } vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);});