瀏覽代碼

Implement detection of mod compatibility patches

Ivan Savenko 1 年之前
父節點
當前提交
d849e53499

+ 5 - 9
lib/json/JsonUtils.cpp

@@ -275,11 +275,8 @@ JsonNode JsonUtils::assembleFromFiles(const std::string & filename)
 	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())
 	{
 		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_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);
-			logMod->warn("Mod '%s' - value %s set to '%s'", left.getModScope(), keyName, left.toCompactString());
-			logMod->warn("Mod '%s' - value %s set to '%s'", right.getModScope(), keyName, right.toCompactString());
+			result[keyName][left.getModScope()] = left;
+			result[keyName][right.getModScope()] = right;
 			return;
 		}
 		case JsonNode::JsonType::DATA_STRUCT:
 		{
-			for(auto & node : left.Struct())
+			for(const auto & node : left.Struct())
 				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);
 		}
 	}
 }

+ 4 - 2
lib/json/JsonUtils.h

@@ -74,8 +74,10 @@ namespace JsonUtils
 	DLL_LINKAGE const JsonNode & getSchema(const std::string & URI);
 
 	/// detects potential conflicts - json entries present in both nodes
-	/// any error messages will be printed to error log
-	DLL_LINKAGE void detectConflicts(const JsonNode & left, const JsonNode & right, const std::string & entityName, const std::string & keyName);
+	/// returns JsonNode that contains list of conflicting keys
+	/// 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

+ 8 - 2
lib/modding/CModHandler.cpp

@@ -392,6 +392,12 @@ std::string CModHandler::getModLanguage(const TModID& modId) const
 	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
 {
 	auto it = allMods.find(modId);
@@ -499,8 +505,8 @@ void CModHandler::load()
 
 	content->loadCustom();
 
-	for(const TModID & modName : activeMods)
-		loadTranslation(modName);
+//	for(const TModID & modName : activeMods)
+//		loadTranslation(modName);
 
 #if 0
 	for(const TModID & modName : activeMods)

+ 1 - 0
lib/modding/CModHandler.h

@@ -66,6 +66,7 @@ public:
 
 	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;
 
 	/// returns list of all (active) mods

+ 41 - 5
lib/modding/ContentTypeHandler.cpp

@@ -39,9 +39,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)
@@ -80,7 +80,7 @@ bool ContentTypeHandler::preloadModData(const std::string & modName, const std::
 			JsonNode & remoteConf = modData[remoteName].patches[objectName];
 
 			if (!remoteConf.isNull())
-				JsonUtils::detectConflicts(remoteConf, entry.second, objectName, "<root>");
+				JsonUtils::detectConflicts(conflictList, remoteConf, entry.second, objectName);
 
 			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){
 		handler->beforeValidate(data);
 		if (validate)
-			result &= JsonUtils::validate(data, "vcmi:" + objectName, name);
+			result &= JsonUtils::validate(data, "vcmi:" + entityName, name);
 	};
 
 	// 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 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;
 		}
 
@@ -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();
 }
 

+ 3 - 1
lib/modding/ContentTypeHandler.h

@@ -19,6 +19,8 @@ class CModInfo;
 /// internal type to handle loading of one data type (e.g. artifacts, creatures)
 class DLL_LINKAGE ContentTypeHandler
 {
+	JsonNode conflictList;
+
 public:
 	struct ModInfo
 	{
@@ -29,7 +31,7 @@ public:
 	};
 	/// handler to which all data will be loaded
 	IHandlerBase * handler;
-	std::string objectName;
+	std::string entityName;
 
 	/// contains all loaded H3 data
 	std::vector<JsonNode> originalData;