|
|
@@ -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,30 +163,69 @@ 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);
|
|
|
+ if (data.second.modData.isNull())
|
|
|
+ {
|
|
|
+ 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.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(auto & otherMod : modData)
|
|
|
+ for (const auto& [conflictPath, conflictModData] : conflictList.Struct())
|
|
|
{
|
|
|
- if (otherMod.first == data.first)
|
|
|
- continue;
|
|
|
+ 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));
|
|
|
|
|
|
- if (otherMod.second.modData.isNull())
|
|
|
- continue;
|
|
|
+ vstd::erase_if(conflictingMods, [&resolvedConflicts](const std::string & entry){ return resolvedConflicts.count(entry);});
|
|
|
|
|
|
- for(auto & otherObject : otherMod.second.modData.Struct())
|
|
|
+ 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 (data.second.modData.Struct().count(otherObject.first))
|
|
|
+ if (conflictModData[modID] != conflictModData[*conflictingMods.begin()])
|
|
|
{
|
|
|
- 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);
|
|
|
+ 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());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -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
|