Browse Source

Fixed mod dependencies check on save loading to prevent crashes:

- mods that don't affect game objects can be ignored when loading save
- gameplay-affecting mods that were present in save must be active
- gameplay-affecting mods that were not in save must not be active
Ivan Savenko 2 years ago
parent
commit
ef0cd0ba6e
2 changed files with 82 additions and 6 deletions
  1. 42 0
      lib/CModHandler.cpp
  2. 40 6
      lib/CModHandler.h

+ 42 - 0
lib/CModHandler.cpp

@@ -649,6 +649,48 @@ void CModInfo::updateChecksum(ui32 newChecksum)
 	}
 }
 
+bool CModInfo::checkModGameplayAffecting() const
+{
+	if (modGameplayAffecting.has_value())
+		return *modGameplayAffecting;
+
+	static const std::vector<std::string> keysToTest = {
+		"heroClasses",
+		"artifacts",
+		"creatures",
+		"factions",
+		"objects",
+		"heroes",
+		"spells",
+		"skills",
+		"templates",
+		"scripts",
+		"battlefields",
+		"terrains",
+		"rivers",
+		"roads",
+		"obstacles"
+	};
+
+	ResourceID modFileResource(CModInfo::getModFile(identifier));
+
+	if(CResourceHandler::get("initial")->existsResource(modFileResource))
+	{
+		const JsonNode modConfig(modFileResource);
+
+		for (auto const & key : keysToTest)
+		{
+			if (!modConfig[key].isNull())
+			{
+				modGameplayAffecting = true;
+				return *modGameplayAffecting;
+			}
+		}
+	}
+	modGameplayAffecting = false;
+	return *modGameplayAffecting;
+}
+
 void CModInfo::loadLocalData(const JsonNode & data)
 {
 	bool validated = false;

+ 40 - 6
lib/CModHandler.h

@@ -179,6 +179,10 @@ using TModID = std::string;
 
 class DLL_LINKAGE CModInfo
 {
+	/// cached result of checkModGameplayAffecting() call
+	/// Do not serialize - depends on local mod version, not server/save mod version
+	mutable std::optional<bool> modGameplayAffecting;
+
 public:
 	enum EValidationStatus
 	{
@@ -223,6 +227,9 @@ public:
 	JsonNode saveLocalData() const;
 	void updateChecksum(ui32 newChecksum);
 
+	/// return true if this mod can affect gameplay, e.g. adds or modifies any game objects
+	bool checkModGameplayAffecting() const;
+
 	bool isEnabled() const;
 	void setEnabled(bool on);
 
@@ -351,19 +358,46 @@ public:
 		else
 		{
 			loadMods();
+			std::vector<TModID> saveActiveMods;
 			std::vector<TModID> newActiveMods;
-			h & newActiveMods;
+			h & saveActiveMods;
 			
 			Incompatibility::ModList missingMods;
-			for(const auto & m : newActiveMods)
 
+			for(const auto & m : activeMods)
+			{
+				if (vstd::contains(saveActiveMods, m))
+					continue;
+
+				auto & modInfo = allMods.at(m);
+				if(modInfo.checkModGameplayAffecting())
+					missingMods.emplace_back(m, modInfo.version.toString());
+			}
+
+			for(const auto & m : saveActiveMods)
 			{
 				CModVersion mver;
 				h & mver;
-				
-				if(allMods.count(m) && (allMods[m].version.isNull() || mver.isNull() || allMods[m].version.compatible(mver)))
-					allMods[m].setEnabled(true);
-				else
+
+				if (allMods.count(m) == 0)
+				{
+					missingMods.emplace_back(m, mver.toString());
+					continue;
+				}
+
+				auto & modInfo = allMods.at(m);
+
+				bool modAffectsGameplay = modInfo.checkModGameplayAffecting();
+				bool modVersionCompatible = modInfo.version.isNull() || mver.isNull() || modInfo.version.compatible(mver);
+				bool modEnabledLocally = vstd::contains(activeMods, m);
+				bool modCanBeEnabled = modEnabledLocally && modVersionCompatible;
+
+				allMods[m].setEnabled(modCanBeEnabled);
+
+				if (modCanBeEnabled)
+					newActiveMods.push_back(m);
+
+				if (!modCanBeEnabled && modAffectsGameplay)
 					missingMods.emplace_back(m, mver.toString());
 			}