Fix chain disabling of mods with soft dependencies

Happens if:
- Main mod that player attempts to disable has submods
- There is another mod that has soft-depend on such submod

This leads to scenario where submod is never added to load order, and
neither is another mod since it can only be added after submod.

Now mods (including submods) with known missing dependencies will be
dropped from load order immediately, allowing mods with soft-depends on
them to be added to load order
This commit is contained in:
Ivan Savenko committed 2025-11-26 21:04:47 +02:00
1 parent e07eceb0c8
commit d5afe8de54
1 file changed
+32 -12
+32 -12
View File
@@ -767,35 +767,45 @@ void ModDependenciesResolver::tryAddMods(TModList modsToResolve, const ModsStora
std::set<TModID> resolvedModIDs(activeMods.begin(), activeMods.end()); // 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
enum class ModResolveStatus {
RESOLVED, // ok - mod can be added to load order
WAITING, // maybe - wait for more iterations before deciding
BROKEN // fail - this mod definitely can't be loaded
};
// Mod is resolved if it has no dependencies or all its dependencies are already resolved
auto isResolved = [&](const ModDescription & mod) -> bool
auto isResolved = [&](const ModDescription & mod) -> ModResolveStatus
{
if (mod.isTranslation() && CGeneralTextHandler::getPreferredLanguage() != mod.getBaseLanguage())
return false;
return ModResolveStatus::BROKEN;
if(!mod.isCompatible())
return false;
if(mod.getDependencies().size() > resolvedModIDs.size())
return false;
return ModResolveStatus::BROKEN;
for(const TModID & dependency : mod.getDependencies())
{
if(!vstd::contains(resolvedModIDs, dependency))
return false;
{
if (vstd::contains(notResolvedModIDs, dependency))
return ModResolveStatus::WAITING;
else
return ModResolveStatus::BROKEN;
}
}
for(const TModID & softDependency : mod.getSoftDependencies())
if(vstd::contains(notResolvedModIDs, softDependency))
return false;
return ModResolveStatus::WAITING;
for(const TModID & conflict : mod.getConflicts())
if(vstd::contains(resolvedModIDs, conflict))
return false;
return ModResolveStatus::BROKEN;
for(const TModID & reverseConflict : resolvedModIDs)
if(vstd::contains(storage.getMod(reverseConflict).getConflicts(), mod.getID()))
return false;
return ModResolveStatus::BROKEN;
return true;
return ModResolveStatus::RESOLVED;
};
while(true)
@@ -803,7 +813,9 @@ void ModDependenciesResolver::tryAddMods(TModList modsToResolve, const ModsStora
std::set<TModID> resolvedOnCurrentTreeLevel;
for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree
{
if(isResolved(storage.getMod(*it)))
ModResolveStatus status = isResolved(storage.getMod(*it));
if (status == ModResolveStatus::RESOLVED)
{
resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node children will be resolved on the next iteration
assert(!vstd::contains(sortedValidMods, *it));
@@ -811,6 +823,14 @@ void ModDependenciesResolver::tryAddMods(TModList modsToResolve, const ModsStora
it = modsToResolve.erase(it);
continue;
}
if (status == ModResolveStatus::BROKEN)
{
resolvedOnCurrentTreeLevel.insert(*it);
brokenMods.push_back(*it);
it = modsToResolve.erase(it);
continue;
}
it++;
}
if(!resolvedOnCurrentTreeLevel.empty())