mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-26 03:52:01 +02:00
Implement detection of mod compatibility patches
This commit is contained in:
parent
d0aba56a5e
commit
d849e53499
@ -275,11 +275,8 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonUtils::detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName)
|
void JsonUtils::detectConflicts(JsonNode & result, const JsonNode & left, const JsonNode & right, const std::string & keyName)
|
||||||
{
|
{
|
||||||
if (left == right)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (left.getType())
|
switch (left.getType())
|
||||||
{
|
{
|
||||||
case JsonNode::JsonType::DATA_NULL:
|
case JsonNode::JsonType::DATA_NULL:
|
||||||
@ -289,16 +286,15 @@ void JsonUtils::detectConflicts(const JsonNode & left, const JsonNode & right, c
|
|||||||
case JsonNode::JsonType::DATA_STRING:
|
case JsonNode::JsonType::DATA_STRING:
|
||||||
case JsonNode::JsonType::DATA_VECTOR: // NOTE: comparing vectors as whole - since merge will overwrite it in its entirety
|
case JsonNode::JsonType::DATA_VECTOR: // NOTE: comparing vectors as whole - since merge will overwrite it in its entirety
|
||||||
{
|
{
|
||||||
logMod->warn("Potential confict detected between '%s' and '%s' in object '%s'", left.getModScope(), right.getModScope(), entityName);
|
result[keyName][left.getModScope()] = left;
|
||||||
logMod->warn("Mod '%s' - value %s set to '%s'", left.getModScope(), keyName, left.toCompactString());
|
result[keyName][right.getModScope()] = right;
|
||||||
logMod->warn("Mod '%s' - value %s set to '%s'", right.getModScope(), keyName, right.toCompactString());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case JsonNode::JsonType::DATA_STRUCT:
|
case JsonNode::JsonType::DATA_STRUCT:
|
||||||
{
|
{
|
||||||
for(auto & node : left.Struct())
|
for(const auto & node : left.Struct())
|
||||||
if (!right[node.first].isNull())
|
if (!right[node.first].isNull())
|
||||||
detectConflicts(node.second, right[node.first], entityName, keyName + "/" + node.first);
|
detectConflicts(result, node.second, right[node.first], keyName + "/" + node.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,10 @@ namespace JsonUtils
|
|||||||
DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
|
DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
|
||||||
|
|
||||||
/// detects potential conflicts - json entries present in both nodes
|
/// detects potential conflicts - json entries present in both nodes
|
||||||
/// any error messages will be printed to error log
|
/// returns JsonNode that contains list of conflicting keys
|
||||||
DLL_LINKAGE void detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName);
|
/// For each conflict - list of conflicting mods and list of conflicting json values
|
||||||
|
/// result[pathToKey][modID] -> node that was conflicting
|
||||||
|
DLL_LINKAGE void detectConflicts(JsonNode & result, const JsonNode & left, const JsonNode & right, const std::string & keyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
VCMI_LIB_NAMESPACE_END
|
VCMI_LIB_NAMESPACE_END
|
||||||
|
@ -392,6 +392,12 @@ std::string CModHandler::getModLanguage(const TModID& modId) const
|
|||||||
return allMods.at(modId).baseLanguage;
|
return allMods.at(modId).baseLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::set<TModID> CModHandler::getModDependencies(const TModID & modId) const
|
||||||
|
{
|
||||||
|
bool isModFound;
|
||||||
|
return getModDependencies(modId, isModFound);
|
||||||
|
}
|
||||||
|
|
||||||
std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const
|
std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const
|
||||||
{
|
{
|
||||||
auto it = allMods.find(modId);
|
auto it = allMods.find(modId);
|
||||||
@ -499,8 +505,8 @@ void CModHandler::load()
|
|||||||
|
|
||||||
content->loadCustom();
|
content->loadCustom();
|
||||||
|
|
||||||
for(const TModID & modName : activeMods)
|
// for(const TModID & modName : activeMods)
|
||||||
loadTranslation(modName);
|
// loadTranslation(modName);
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
for(const TModID & modName : activeMods)
|
for(const TModID & modName : activeMods)
|
||||||
|
@ -66,6 +66,7 @@ public:
|
|||||||
|
|
||||||
std::string getModLanguage(const TModID & modId) const;
|
std::string getModLanguage(const TModID & modId) const;
|
||||||
|
|
||||||
|
std::set<TModID> getModDependencies(const TModID & modId) const;
|
||||||
std::set<TModID> getModDependencies(const TModID & modId, bool & isModFound) const;
|
std::set<TModID> getModDependencies(const TModID & modId, bool & isModFound) const;
|
||||||
|
|
||||||
/// returns list of all (active) mods
|
/// returns list of all (active) mods
|
||||||
|
@ -39,9 +39,9 @@
|
|||||||
|
|
||||||
VCMI_LIB_NAMESPACE_BEGIN
|
VCMI_LIB_NAMESPACE_BEGIN
|
||||||
|
|
||||||
ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & objectName):
|
ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, const std::string & entityName):
|
||||||
handler(handler),
|
handler(handler),
|
||||||
objectName(objectName),
|
entityName(entityName),
|
||||||
originalData(handler->loadLegacyData())
|
originalData(handler->loadLegacyData())
|
||||||
{
|
{
|
||||||
for(auto & node : originalData)
|
for(auto & node : originalData)
|
||||||
@ -80,7 +80,7 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std::
|
|||||||
JsonNode & remoteConf = modData[remoteName].patches[objectName];
|
JsonNode & remoteConf = modData[remoteName].patches[objectName];
|
||||||
|
|
||||||
if (!remoteConf.isNull())
|
if (!remoteConf.isNull())
|
||||||
JsonUtils::detectConflicts(remoteConf, entry.second, objectName, "<root>");
|
JsonUtils::detectConflicts(conflictList, remoteConf, entry.second, objectName);
|
||||||
|
|
||||||
JsonUtils::merge(remoteConf, entry.second);
|
JsonUtils::merge(remoteConf, entry.second);
|
||||||
}
|
}
|
||||||
@ -96,7 +96,7 @@ bool ContentTypeHandler::loadMod(const std::string & modName, bool validate)
|
|||||||
auto performValidate = [&,this](JsonNode & data, const std::string & name){
|
auto performValidate = [&,this](JsonNode & data, const std::string & name){
|
||||||
handler->beforeValidate(data);
|
handler->beforeValidate(data);
|
||||||
if (validate)
|
if (validate)
|
||||||
result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
|
result &= JsonUtils::validate(data, "vcmi:" + entityName, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// apply patches
|
// apply patches
|
||||||
@ -116,7 +116,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 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
|
// - 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
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +189,42 @@ void ContentTypeHandler::afterLoadFinalization()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
handler->afterLoadFinalization();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ class CModInfo;
|
|||||||
/// internal type to handle loading of one data type (e.g. artifacts, creatures)
|
/// internal type to handle loading of one data type (e.g. artifacts, creatures)
|
||||||
class DLL_LINKAGE ContentTypeHandler
|
class DLL_LINKAGE ContentTypeHandler
|
||||||
{
|
{
|
||||||
|
JsonNode conflictList;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct ModInfo
|
struct ModInfo
|
||||||
{
|
{
|
||||||
@ -29,7 +31,7 @@ public:
|
|||||||
};
|
};
|
||||||
/// handler to which all data will be loaded
|
/// handler to which all data will be loaded
|
||||||
IHandlerBase * handler;
|
IHandlerBase * handler;
|
||||||
std::string objectName;
|
std::string entityName;
|
||||||
|
|
||||||
/// contains all loaded H3 data
|
/// contains all loaded H3 data
|
||||||
std::vector<JsonNode> originalData;
|
std::vector<JsonNode> originalData;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user