mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-27 22:49:25 +02:00
Merge pull request #4712 from IvanSavenko/detect_conflict
Detection of potential conflicts between mods
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "../BattleFieldHandler.h"
|
||||
#include "../CArtHandler.h"
|
||||
#include "../CCreatureHandler.h"
|
||||
#include "../CConfigHandler.h"
|
||||
#include "../entities/faction/CTownHandler.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../CHeroHandler.h"
|
||||
@@ -39,9 +40,9 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & objectName):
|
||||
ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & entityName):
|
||||
handler(handler),
|
||||
objectName(objectName),
|
||||
entityName(entityName),
|
||||
originalData(handler->loadLegacyData())
|
||||
{
|
||||
for(auto & node : originalData)
|
||||
@@ -79,6 +80,9 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const JsonN
|
||||
logMod->trace("Patching object %s (%s) from %s", objectName, remoteName, modName);
|
||||
JsonNode & remoteConf = modData[remoteName].patches[objectName];
|
||||
|
||||
if (!remoteConf.isNull() && settings["mods"]["validation"].String() != "off")
|
||||
JsonUtils::detectConflicts(conflictList, remoteConf, entry.second, objectName);
|
||||
|
||||
JsonUtils::merge(remoteConf, entry.second);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +97,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
|
||||
auto performValidate = [&,this](JsonNode & data, const std::string & name){
|
||||
handler->beforeValidate(data);
|
||||
if (validate)
|
||||
result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
|
||||
result &= JsonUtils::validate(data, "vcmi:" + entityName, name);
|
||||
};
|
||||
|
||||
// apply patches
|
||||
@@ -113,7 +117,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
|
||||
// - another mod attempts to add object into this mod (technically can be supported, but might lead to weird edge cases)
|
||||
// - another mod attempts to edit object from this mod that no longer exist - DANGER since such patch likely has very incomplete data
|
||||
// so emit warning and skip such case
|
||||
logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, objectName, modName);
|
||||
logMod->warn("Mod '%s' attempts to edit object '%s' of type '%s' from mod '%s' but no such object exist!", data.getModScope(), name, entityName, modName);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -159,31 +163,70 @@ void ContentTypeHandler::loadCustom()
|
||||
|
||||
void ContentTypeHandler::afterLoadFinalization()
|
||||
{
|
||||
for (auto const & data : modData)
|
||||
if (settings["mods"]["validation"].String() != "off")
|
||||
{
|
||||
if (data.second.modData.isNull())
|
||||
for (auto const & data : modData)
|
||||
{
|
||||
for (auto node : data.second.patches.Struct())
|
||||
logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first);
|
||||
}
|
||||
|
||||
for(auto & otherMod : modData)
|
||||
{
|
||||
if (otherMod.first == data.first)
|
||||
continue;
|
||||
|
||||
if (otherMod.second.modData.isNull())
|
||||
continue;
|
||||
|
||||
for(auto & otherObject : otherMod.second.modData.Struct())
|
||||
if (data.second.modData.isNull())
|
||||
{
|
||||
if (data.second.modData.Struct().count(otherObject.first))
|
||||
for (auto node : data.second.patches.Struct())
|
||||
logMod->warn("Mod '%s' have added patch for object '%s' from mod '%s', but this mod was not loaded or has no new objects.", node.second.getModScope(), node.first, data.first);
|
||||
}
|
||||
|
||||
for(auto & otherMod : modData)
|
||||
{
|
||||
if (otherMod.first == data.first)
|
||||
continue;
|
||||
|
||||
if (otherMod.second.modData.isNull())
|
||||
continue;
|
||||
|
||||
for(auto & otherObject : otherMod.second.modData.Struct())
|
||||
{
|
||||
logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first);
|
||||
logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first);
|
||||
if (data.second.modData.Struct().count(otherObject.first))
|
||||
{
|
||||
logMod->warn("Mod '%s' have added object with name '%s' that is also available in mod '%s'", data.first, otherObject.first, otherMod.first);
|
||||
logMod->warn("Two objects with same name were loaded. Please use form '%s:%s' if mod '%s' needs to modify this object instead", otherMod.first, otherObject.first, data.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [conflictPath, conflictModData] : conflictList.Struct())
|
||||
{
|
||||
std::set<std::string> conflictingMods;
|
||||
std::set<std::string> resolvedConflicts;
|
||||
|
||||
for (auto const & conflictModData : conflictModData.Struct())
|
||||
conflictingMods.insert(conflictModData.first);
|
||||
|
||||
for (auto const & modID : conflictingMods)
|
||||
resolvedConflicts.merge(VLC->modh->getModDependencies(modID));
|
||||
|
||||
vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);});
|
||||
|
||||
if (conflictingMods.size() < 2)
|
||||
continue; // all conflicts were resolved - either via compatibility patch (mod that depends on 2 conflicting mods) or simple mod that depends on another one
|
||||
|
||||
bool allEqual = true;
|
||||
|
||||
for (auto const & modID : conflictingMods)
|
||||
{
|
||||
if (conflictModData[modID] != conflictModData[*conflictingMods.begin()])
|
||||
{
|
||||
allEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allEqual)
|
||||
continue; // conflict still present, but all mods use the same value for conflicting entry - permit it
|
||||
|
||||
logMod->warn("Potential confict in '%s'", conflictPath);
|
||||
|
||||
for (auto const & modID : conflictingMods)
|
||||
logMod->warn("Mod '%s' - value set to %s", modID, conflictModData[modID].toCompactString());
|
||||
}
|
||||
}
|
||||
|
||||
handler->afterLoadFinalization();
|
||||
@@ -249,7 +292,7 @@ void CContentHandler::afterLoadFinalization()
|
||||
|
||||
void CContentHandler::preloadData(CModInfo & mod)
|
||||
{
|
||||
bool validate = (mod.validation != CModInfo::PASSED);
|
||||
bool validate = validateMod(mod);
|
||||
|
||||
// print message in format [<8-symbols checksum>] <modname>
|
||||
auto & info = mod.getVerificationInfo();
|
||||
@@ -266,7 +309,7 @@ void CContentHandler::preloadData(CModInfo & mod)
|
||||
|
||||
void CContentHandler::load(CModInfo & mod)
|
||||
{
|
||||
bool validate = (mod.validation != CModInfo::PASSED);
|
||||
bool validate = validateMod(mod);
|
||||
|
||||
if (!loadMod(mod.identifier, validate))
|
||||
mod.validation = CModInfo::FAILED;
|
||||
@@ -287,4 +330,18 @@ const ContentTypeHandler & CContentHandler::operator[](const std::string & name)
|
||||
return handlers.at(name);
|
||||
}
|
||||
|
||||
bool CContentHandler::validateMod(const CModInfo & mod) const
|
||||
{
|
||||
if (settings["mods"]["validation"].String() == "full")
|
||||
return true;
|
||||
|
||||
if (mod.validation == CModInfo::PASSED)
|
||||
return false;
|
||||
|
||||
if (settings["mods"]["validation"].String() == "off")
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
Reference in New Issue
Block a user