浏览代码

add support for soft dependencies

kdmcser 1 年之前
父节点
当前提交
3b72594743

+ 1 - 0
AUTHORS.h

@@ -46,6 +46,7 @@ const std::vector<std::vector<std::string>> contributors = {
    { "Developing", "",                     "vmarkovtsev",           ""                             },
    { "Developing", "Tom Zielinski",        "Warmonger",             "[email protected]"              },
    { "Developing", "Xiaomin Ding",         "",                      "[email protected]"        },
+   { "Developing", "Fenghuang Rumeng",     "kdmcser",               "[email protected]"             },
 
    { "Testing",    "Ben Yan",              "by003",                 "[email protected],"        },
    { "Testing",    "",                     "Misiokles",             ""                             },

+ 1 - 0
Mods/vcmi/config/vcmi/chinese.json

@@ -149,6 +149,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版本不兼容!",
 

+ 1 - 0
Mods/vcmi/config/vcmi/english.json

@@ -159,6 +159,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!",
 	

+ 5 - 0
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",

+ 6 - 0
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" :

+ 58 - 3
lib/modding/CModHandler.cpp

@@ -87,8 +87,9 @@ std::vector <TModID> CModHandler::validateAndSortDependencies(std::vector <TModI
 	std::vector <TModID> sortedValidMods; // Vector keeps order of elements (LIFO)
 	sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation
 	std::set <TModID> resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements
+	std::set <TModID> 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 <TModID> CModHandler::validateAndSortDependencies(std::vector <TModI
 				return false;
 		}
 
+		for(const TModID & softDependency : mod.softDependencies)
+		{
+			if(vstd::contains(notResolvedModIDs, softDependency))
+				return false;
+		}
+
 		for(const TModID & conflict : mod.conflicts)
 		{
 			if(vstd::contains(resolvedModIDs, conflict))
@@ -130,9 +137,11 @@ std::vector <TModID> CModHandler::validateAndSortDependencies(std::vector <TModI
 		if(!resolvedOnCurrentTreeLevel.empty())
 		{
 			resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end());
-					continue;
+			for(auto it = resolvedOnCurrentTreeLevel.begin(); it != resolvedOnCurrentTreeLevel.end(); it++)
+				notResolvedModIDs.erase(*it);
+			continue;
 		}
-		// If there're no valid mods on the current mods tree level, no more mod can be resolved, should be end.
+		// If there are no valid mods on the current mods tree level, no more mod can be resolved, should be ended.
 		break;
 	}
 
@@ -158,21 +167,42 @@ std::vector <TModID> CModHandler::validateAndSortDependencies(std::vector <TModI
 	for(const auto & brokenModID : modsToResolve)
 	{
 		const CModInfo & brokenMod = allMods.at(brokenModID);
+		bool showErrorMessage = false;
 		for(const TModID & dependency : brokenMod.dependencies)
 		{
 			if(!vstd::contains(resolvedModIDs, dependency) && brokenMod.config["modType"].String() != "Compatibility")
+			{
 				addErrorMessage("vcmi.server.errors.modNoDependency", brokenModID, dependency);
+				showErrorMessage = true;
+			}
 		}
 		for(const TModID & conflict : brokenMod.conflicts)
 		{
 			if(vstd::contains(resolvedModIDs, conflict))
+			{
 				addErrorMessage("vcmi.server.errors.modConflict", brokenModID, conflict);
+				showErrorMessage = true;
+			}
 		}
 		for(const TModID & reverseConflict : resolvedModIDs)
 		{
 			if (vstd::contains(allMods.at(reverseConflict).conflicts, brokenModID))
+			{
 				addErrorMessage("vcmi.server.errors.modConflict", brokenModID, reverseConflict);
+				showErrorMessage = true;
+			}
 		}
+
+		// some mods may in a (soft) dependency loop.
+		if(!showErrorMessage && brokenMod.config["modType"].String() != "Compatibility")
+		{
+			modLoadErrors->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<ResourcePath> leftResources = modFilesystems[leftModName]->getFilteredFiles(filter);
@@ -417,6 +450,28 @@ std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & is
 	return {};
 }
 
+std::set<TModID> 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<TModID> CModHandler::getModEnabledSoftDependencies(const TModID & modId) const
+{
+	std::set<TModID> 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"]));

+ 2 - 0
lib/modding/CModHandler.h

@@ -66,6 +66,8 @@ public:
 
 	std::set<TModID> getModDependencies(const TModID & modId) const;
 	std::set<TModID> getModDependencies(const TModID & modId, bool & isModFound) const;
+	std::set<TModID> getModSoftDependencies(const TModID & modId) const;
+	std::set<TModID> getModEnabledSoftDependencies(const TModID & modId) const;
 
 	/// returns list of all (active) mods
 	std::vector<std::string> getAllMods() const;

+ 1 - 0
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),

+ 3 - 0
lib/modding/CModInfo.h

@@ -44,6 +44,9 @@ public:
 	/// list of mods that should be loaded before this one
 	std::set <TModID> 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 <TModID> softDependencies;
+
 	/// list of mods that can't be used in the same time as this one
 	std::set <TModID> conflicts;
 

+ 3 - 1
lib/modding/ContentTypeHandler.cpp

@@ -201,8 +201,10 @@ void ContentTypeHandler::afterLoadFinalization()
 			for (auto const & conflictModEntry: conflictModData.Struct())
 				conflictingMods.insert(conflictModEntry.first);
 
-			for (auto const & modID : conflictingMods)
+			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);});