1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-21 17:17:06 +02:00

add support for soft dependencies

This commit is contained in:
kdmcser 2024-10-25 23:21:32 +08:00
parent 1826b5bbdf
commit 3b72594743
10 changed files with 81 additions and 4 deletions

View File

@ -46,6 +46,7 @@ const std::vector<std::vector<std::string>> 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", "" },

View File

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

View File

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

View File

@ -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",

View File

@ -49,6 +49,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" :
[

View File

@ -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());
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,22 +167,43 @@ 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"]));

View File

@ -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;

View File

@ -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),

View File

@ -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;

View File

@ -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);});