mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Mod management rework, part 1
- Replaced CModInfo class with constant ModDescription class - Simplified mod loading logic - Extracted some functionality from ModHandler into separate classes for future reuse by Launcher
This commit is contained in:
		| @@ -27,7 +27,7 @@ | ||||
| #include "../widgets/ObjectLists.h" | ||||
|  | ||||
| #include "../../lib/modding/CModHandler.h" | ||||
| #include "../../lib/modding/CModInfo.h" | ||||
| #include "../../lib/modding/ModDescription.h" | ||||
| #include "../../lib/texts/CGeneralTextHandler.h" | ||||
| #include "../../lib/texts/MetaString.h" | ||||
|  | ||||
| @@ -128,14 +128,14 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s | ||||
| 		GlobalLobbyRoomModInfo modInfo; | ||||
| 		modInfo.status = modEntry.second; | ||||
| 		if (modEntry.second == ModVerificationStatus::EXCESSIVE) | ||||
| 			modInfo.version = CGI->modh->getModInfo(modEntry.first).getVerificationInfo().version.toString(); | ||||
| 			modInfo.version = CGI->modh->getModInfo(modEntry.first).getVersion().toString(); | ||||
| 		else | ||||
| 			modInfo.version = roomDescription.modList.at(modEntry.first).version.toString(); | ||||
|  | ||||
| 		if (modEntry.second == ModVerificationStatus::NOT_INSTALLED) | ||||
| 			modInfo.modName = roomDescription.modList.at(modEntry.first).name; | ||||
| 		else | ||||
| 			modInfo.modName = CGI->modh->getModInfo(modEntry.first).getVerificationInfo().name; | ||||
| 			modInfo.modName = CGI->modh->getModInfo(modEntry.first).getName(); | ||||
|  | ||||
| 		modVerificationList.push_back(modInfo); | ||||
| 	} | ||||
|   | ||||
| @@ -14,7 +14,6 @@ | ||||
| #include "../../lib/filesystem/Filesystem.h" | ||||
| #include "../../lib/filesystem/CZipLoader.h" | ||||
| #include "../../lib/modding/CModHandler.h" | ||||
| #include "../../lib/modding/CModInfo.h" | ||||
| #include "../../lib/modding/IdentifierStorage.h" | ||||
|  | ||||
| #include "../vcmiqt/jsonutils.h" | ||||
| @@ -90,37 +89,32 @@ void CModManager::loadRepositories(QVector<QVariantMap> repomap) | ||||
| void CModManager::loadMods() | ||||
| { | ||||
| 	CModHandler handler; | ||||
| 	handler.loadMods(); | ||||
| 	auto installedMods = handler.getAllMods(); | ||||
| 	localMods.clear(); | ||||
|  | ||||
| 	for(auto modname : installedMods) | ||||
| 	{ | ||||
| 		auto resID = CModInfo::getModFile(modname); | ||||
| 		if(CResourceHandler::get()->existsResource(resID)) | ||||
| 		{ | ||||
| 			//calculate mod size | ||||
| 			qint64 total = 0; | ||||
| 			ResourcePath resDir(CModInfo::getModDir(modname), EResType::DIRECTORY); | ||||
| 			if(CResourceHandler::get()->existsResource(resDir)) | ||||
| 			{ | ||||
| 				for(QDirIterator iter(QString::fromStdString(CResourceHandler::get()->getResourceName(resDir)->string()), QDirIterator::Subdirectories); iter.hasNext(); iter.next()) | ||||
| 					total += iter.fileInfo().size(); | ||||
| 			} | ||||
| 			 | ||||
| 			boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID); | ||||
| 			auto mod = JsonUtils::JsonFromFile(pathToQString(name)); | ||||
| 			auto json = JsonUtils::toJson(mod); | ||||
| 			json["localSizeBytes"].Float() = total; | ||||
| 			if(!name.is_absolute()) | ||||
| 				json["storedLocally"].Bool() = true; | ||||
|  | ||||
| 			mod = JsonUtils::toVariant(json); | ||||
| 			QString modNameQt = QString::fromUtf8(modname.c_str()).toLower(); | ||||
| 			localMods.insert(modNameQt, mod); | ||||
| 			modSettings->registerNewMod(modNameQt, json["keepDisabled"].Bool()); | ||||
| 		} | ||||
| 	} | ||||
| //	for(auto modname : installedMods) | ||||
| //	{ | ||||
| //			//calculate mod size | ||||
| //			qint64 total = 0; | ||||
| //			ResourcePath resDir(CModInfo::getModDir(modname), EResType::DIRECTORY); | ||||
| //			if(CResourceHandler::get()->existsResource(resDir)) | ||||
| //			{ | ||||
| //				for(QDirIterator iter(QString::fromStdString(CResourceHandler::get()->getResourceName(resDir)->string()), QDirIterator::Subdirectories); iter.hasNext(); iter.next()) | ||||
| //					total += iter.fileInfo().size(); | ||||
| //			} | ||||
| // | ||||
| //			boost::filesystem::path name = *CResourceHandler::get()->getResourceName(resID); | ||||
| //			auto mod = JsonUtils::JsonFromFile(pathToQString(name)); | ||||
| //			auto json = JsonUtils::toJson(mod); | ||||
| //			json["localSizeBytes"].Float() = total; | ||||
| //			if(!name.is_absolute()) | ||||
| //				json["storedLocally"].Bool() = true; | ||||
| // | ||||
| //			mod = JsonUtils::toVariant(json); | ||||
| //			QString modNameQt = QString::fromUtf8(modname.c_str()).toLower(); | ||||
| //			localMods.insert(modNameQt, mod); | ||||
| //			modSettings->registerNewMod(modNameQt, json["keepDisabled"].Bool()); | ||||
| //	} | ||||
| 	modList->setLocalModList(localMods); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -157,10 +157,11 @@ set(lib_MAIN_SRCS | ||||
|  | ||||
| 	modding/ActiveModsInSaveList.cpp | ||||
| 	modding/CModHandler.cpp | ||||
| 	modding/CModInfo.cpp | ||||
| 	modding/CModVersion.cpp | ||||
| 	modding/ContentTypeHandler.cpp | ||||
| 	modding/IdentifierStorage.cpp | ||||
| 	modding/ModDescription.cpp | ||||
| 	modding/ModManager.cpp | ||||
| 	modding/ModUtility.cpp | ||||
| 	modding/ModVerificationInfo.cpp | ||||
|  | ||||
| @@ -547,11 +548,12 @@ set(lib_MAIN_HEADERS | ||||
|  | ||||
| 	modding/ActiveModsInSaveList.h | ||||
| 	modding/CModHandler.h | ||||
| 	modding/CModInfo.h | ||||
| 	modding/CModVersion.h | ||||
| 	modding/ContentTypeHandler.h | ||||
| 	modding/IdentifierStorage.h | ||||
| 	modding/ModDescription.h | ||||
| 	modding/ModIncompatibility.h | ||||
| 	modding/ModManager.h | ||||
| 	modding/ModScope.h | ||||
| 	modding/ModUtility.h | ||||
| 	modding/ModVerificationInfo.h | ||||
|   | ||||
| @@ -41,7 +41,6 @@ | ||||
| #include "gameState/QuestInfo.h" | ||||
| #include "mapping/CMap.h" | ||||
| #include "modding/CModHandler.h" | ||||
| #include "modding/CModInfo.h" | ||||
| #include "modding/IdentifierStorage.h" | ||||
| #include "modding/CModVersion.h" | ||||
| #include "modding/ActiveModsInSaveList.h" | ||||
|   | ||||
| @@ -26,7 +26,6 @@ | ||||
| #include "entities/hero/CHeroHandler.h" | ||||
| #include "texts/CGeneralTextHandler.h" | ||||
| #include "modding/CModHandler.h" | ||||
| #include "modding/CModInfo.h" | ||||
| #include "modding/IdentifierStorage.h" | ||||
| #include "modding/CModVersion.h" | ||||
| #include "IGameEventsReceiver.h" | ||||
| @@ -157,7 +156,6 @@ void LibClasses::loadModFilesystem() | ||||
| 	CStopWatch loadTime; | ||||
| 	modh = std::make_unique<CModHandler>(); | ||||
| 	identifiersHandler = std::make_unique<CIdentifierStorage>(); | ||||
| 	modh->loadMods(); | ||||
| 	logGlobal->info("\tMod handler: %d ms", loadTime.getDiff()); | ||||
|  | ||||
| 	modh->loadModFilesystems(); | ||||
|   | ||||
| @@ -212,6 +212,7 @@ ISimpleResourceLoader * CResourceHandler::get() | ||||
|  | ||||
| ISimpleResourceLoader * CResourceHandler::get(const std::string & identifier) | ||||
| { | ||||
| 	assert(knownLoaders.count(identifier)); | ||||
| 	return knownLoaders.at(identifier); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -17,8 +17,8 @@ | ||||
| #include "../filesystem/CMemoryStream.h" | ||||
| #include "../filesystem/CMemoryBuffer.h" | ||||
| #include "../modding/CModHandler.h" | ||||
| #include "../modding/ModDescription.h" | ||||
| #include "../modding/ModScope.h" | ||||
| #include "../modding/CModInfo.h" | ||||
| #include "../VCMI_Lib.h" | ||||
|  | ||||
| #include "CMap.h" | ||||
| @@ -99,7 +99,7 @@ ModCompatibilityInfo CMapService::verifyMapHeaderMods(const CMapHeader & map) | ||||
| 		if(vstd::contains(activeMods, mapMod.first)) | ||||
| 		{ | ||||
| 			const auto & modInfo = VLC->modh->getModInfo(mapMod.first); | ||||
| 			if(modInfo.getVerificationInfo().version.compatible(mapMod.second.version)) | ||||
| 			if(modInfo.getVersion().compatible(mapMod.second.version)) | ||||
| 				continue; | ||||
| 		} | ||||
| 		missingMods[mapMod.first] = mapMod.second; | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
| #include "ActiveModsInSaveList.h" | ||||
|  | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "CModInfo.h" | ||||
| #include "ModDescription.h" | ||||
| #include "CModHandler.h" | ||||
| #include "ModIncompatibility.h" | ||||
|  | ||||
| @@ -21,13 +21,13 @@ std::vector<TModID> ActiveModsInSaveList::getActiveGameplayAffectingMods() | ||||
| { | ||||
| 	std::vector<TModID> result; | ||||
| 	for (auto const & entry : VLC->modh->getActiveMods()) | ||||
| 		if (VLC->modh->getModInfo(entry).checkModGameplayAffecting()) | ||||
| 		if (VLC->modh->getModInfo(entry).affectsGameplay()) | ||||
| 			result.push_back(entry); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| const ModVerificationInfo & ActiveModsInSaveList::getVerificationInfo(TModID mod) | ||||
| ModVerificationInfo ActiveModsInSaveList::getVerificationInfo(TModID mod) | ||||
| { | ||||
| 	return VLC->modh->getModInfo(mod).getVerificationInfo(); | ||||
| } | ||||
| @@ -44,10 +44,10 @@ void ActiveModsInSaveList::verifyActiveMods(const std::map<TModID, ModVerificati | ||||
| 			missingMods.push_back(modList.at(compared.first).name); | ||||
|  | ||||
| 		if (compared.second == ModVerificationStatus::DISABLED) | ||||
| 			missingMods.push_back(VLC->modh->getModInfo(compared.first).getVerificationInfo().name); | ||||
| 			missingMods.push_back(VLC->modh->getModInfo(compared.first).getName()); | ||||
|  | ||||
| 		if (compared.second == ModVerificationStatus::EXCESSIVE) | ||||
| 			excessiveMods.push_back(VLC->modh->getModInfo(compared.first).getVerificationInfo().name); | ||||
| 			excessiveMods.push_back(VLC->modh->getModInfo(compared.first).getName()); | ||||
| 	} | ||||
|  | ||||
| 	if(!missingMods.empty() || !excessiveMods.empty()) | ||||
|   | ||||
| @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN | ||||
| class ActiveModsInSaveList | ||||
| { | ||||
| 	std::vector<TModID> getActiveGameplayAffectingMods(); | ||||
| 	const ModVerificationInfo & getVerificationInfo(TModID mod); | ||||
| 	ModVerificationInfo getVerificationInfo(TModID mod); | ||||
|  | ||||
| 	/// Checks whether provided mod list is compatible with current VLC and throws on failure | ||||
| 	void verifyActiveMods(const std::map<TModID, ModVerificationInfo> & modList); | ||||
| @@ -29,7 +29,10 @@ public: | ||||
| 			std::vector<TModID> activeMods = getActiveGameplayAffectingMods(); | ||||
| 			h & activeMods; | ||||
| 			for(const auto & m : activeMods) | ||||
| 				h & getVerificationInfo(m); | ||||
| 			{ | ||||
| 				ModVerificationInfo info = getVerificationInfo(m); | ||||
| 				h & info; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
|   | ||||
| @@ -10,316 +10,49 @@ | ||||
| #include "StdInc.h" | ||||
| #include "CModHandler.h" | ||||
|  | ||||
| #include "CModInfo.h" | ||||
| #include "ModScope.h" | ||||
| #include "ContentTypeHandler.h" | ||||
| #include "IdentifierStorage.h" | ||||
| #include "ModIncompatibility.h" | ||||
| #include "ModDescription.h" | ||||
| #include "ModManager.h" | ||||
| #include "ModScope.h" | ||||
|  | ||||
| #include "../CCreatureHandler.h" | ||||
| #include "../CConfigHandler.h" | ||||
| #include "../CStopWatch.h" | ||||
| #include "../CCreatureHandler.h" | ||||
| #include "../GameSettings.h" | ||||
| #include "../ScriptHandler.h" | ||||
| #include "../constants/StringConstants.h" | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "../filesystem/Filesystem.h" | ||||
| #include "../json/JsonUtils.h" | ||||
| #include "../spells/CSpellHandler.h" | ||||
| #include "../texts/CGeneralTextHandler.h" | ||||
| #include "../texts/Languages.h" | ||||
| #include "../VCMI_Lib.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| static JsonNode loadModSettings(const JsonPath & path) | ||||
| { | ||||
| 	if (CResourceHandler::get("local")->existsResource(ResourcePath(path))) | ||||
| 	{ | ||||
| 		return JsonNode(path); | ||||
| 	} | ||||
| 	// Probably new install. Create initial configuration | ||||
| 	CResourceHandler::get("local")->createResource(path.getOriginalName() + ".json"); | ||||
| 	return JsonNode(); | ||||
| } | ||||
|  | ||||
| CModHandler::CModHandler() | ||||
| 	: content(std::make_shared<CContentHandler>()) | ||||
| 	, coreMod(std::make_unique<CModInfo>()) | ||||
| 	, modManager(std::make_unique<ModManager>()) | ||||
| { | ||||
| } | ||||
|  | ||||
| CModHandler::~CModHandler() = default; | ||||
|  | ||||
| // currentList is passed by value to get current list of depending mods | ||||
| bool CModHandler::hasCircularDependency(const TModID & modID, std::set<TModID> currentList) const | ||||
| { | ||||
| 	const CModInfo & mod = allMods.at(modID); | ||||
|  | ||||
| 	// Mod already present? We found a loop | ||||
| 	if (vstd::contains(currentList, modID)) | ||||
| 	{ | ||||
| 		logMod->error("Error: Circular dependency detected! Printing dependency list:"); | ||||
| 		logMod->error("\t%s -> ", mod.getVerificationInfo().name); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	currentList.insert(modID); | ||||
|  | ||||
| 	// recursively check every dependency of this mod | ||||
| 	for(const TModID & dependency : mod.dependencies) | ||||
| 	{ | ||||
| 		if (hasCircularDependency(dependency, currentList)) | ||||
| 		{ | ||||
| 			logMod->error("\t%s ->\n", mod.getVerificationInfo().name); // conflict detected, print dependency list | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| // Returned vector affects the resource loaders call order (see CFilesystemList::load). | ||||
| // The loaders call order matters when dependent mod overrides resources in its dependencies. | ||||
| std::vector <TModID> CModHandler::validateAndSortDependencies(std::vector <TModID> modsToResolve) const | ||||
| { | ||||
| 	// Topological sort algorithm. | ||||
| 	// TODO: Investigate possible ways to improve performance. | ||||
| 	boost::range::sort(modsToResolve); // Sort mods per name | ||||
| 	std::vector <TModID> sortedValidMods; // Vector keeps order of elements (LIFO) | ||||
| 	sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation | ||||
| 	std::set <TModID> resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements | ||||
| 	std::set <TModID> notResolvedModIDs(modsToResolve.begin(), modsToResolve.end()); // Use a set for validation for performance reason | ||||
|  | ||||
| 	// Mod is resolved if it has no dependencies or all its dependencies are already resolved | ||||
| 	auto isResolved = [&](const CModInfo & mod) -> bool | ||||
| 	{ | ||||
| 		if(mod.dependencies.size() > resolvedModIDs.size()) | ||||
| 			return false; | ||||
|  | ||||
| 		for(const TModID & dependency : mod.dependencies) | ||||
| 		{ | ||||
| 			if(!vstd::contains(resolvedModIDs, dependency)) | ||||
| 				return false; | ||||
| 		} | ||||
|  | ||||
| 		for(const TModID & softDependency : mod.softDependencies) | ||||
| 		{ | ||||
| 			if(vstd::contains(notResolvedModIDs, softDependency)) | ||||
| 				return false; | ||||
| 		} | ||||
|  | ||||
| 		for(const TModID & conflict : mod.conflicts) | ||||
| 		{ | ||||
| 			if(vstd::contains(resolvedModIDs, conflict)) | ||||
| 				return false; | ||||
| 		} | ||||
| 		for(const TModID & reverseConflict : resolvedModIDs) | ||||
| 		{ | ||||
| 			if (vstd::contains(allMods.at(reverseConflict).conflicts, mod.identifier)) | ||||
| 				return false; | ||||
| 		} | ||||
| 		return true; | ||||
| 	}; | ||||
|  | ||||
| 	while(true) | ||||
| 	{ | ||||
| 		std::set <TModID> resolvedOnCurrentTreeLevel; | ||||
| 		for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree | ||||
| 		{ | ||||
| 			if(isResolved(allMods.at(*it))) | ||||
| 			{ | ||||
| 				resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node children will be resolved on the next iteration | ||||
| 				sortedValidMods.push_back(*it); | ||||
| 				it = modsToResolve.erase(it); | ||||
| 				continue; | ||||
| 			} | ||||
| 			it++; | ||||
| 		} | ||||
| 		if(!resolvedOnCurrentTreeLevel.empty()) | ||||
| 		{ | ||||
| 			resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end()); | ||||
| 			for(const auto & it : resolvedOnCurrentTreeLevel) | ||||
| 				notResolvedModIDs.erase(it); | ||||
| 			continue; | ||||
| 		} | ||||
| 		// If there are no valid mods on the current mods tree level, no more mod can be resolved, should be ended. | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	modLoadErrors = std::make_unique<MetaString>(); | ||||
|  | ||||
| 	auto addErrorMessage = [this](const std::string & textID, const std::string & brokenModID, const std::string & missingModID) | ||||
| 	{ | ||||
| 		modLoadErrors->appendTextID(textID); | ||||
|  | ||||
| 		if (allMods.count(brokenModID)) | ||||
| 			modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); | ||||
| 		else | ||||
| 			modLoadErrors->replaceRawString(brokenModID); | ||||
|  | ||||
| 		if (allMods.count(missingModID)) | ||||
| 			modLoadErrors->replaceRawString(allMods.at(missingModID).getVerificationInfo().name); | ||||
| 		else | ||||
| 			modLoadErrors->replaceRawString(missingModID); | ||||
|  | ||||
| 	}; | ||||
|  | ||||
| 	// Left mods have unresolved dependencies, output all to log. | ||||
| 	for(const auto & brokenModID : modsToResolve) | ||||
| 	{ | ||||
| 		const CModInfo & brokenMod = allMods.at(brokenModID); | ||||
| 		bool showErrorMessage = false; | ||||
| 		for(const TModID & dependency : brokenMod.dependencies) | ||||
| 		{ | ||||
| 			if(!vstd::contains(resolvedModIDs, dependency) && brokenMod.config["modType"].String() != "Compatibility") | ||||
| 			{ | ||||
| 				addErrorMessage("vcmi.server.errors.modNoDependency", brokenModID, dependency); | ||||
| 				showErrorMessage = true; | ||||
| 			} | ||||
| 		} | ||||
| 		for(const TModID & conflict : brokenMod.conflicts) | ||||
| 		{ | ||||
| 			if(vstd::contains(resolvedModIDs, conflict)) | ||||
| 			{ | ||||
| 				addErrorMessage("vcmi.server.errors.modConflict", brokenModID, conflict); | ||||
| 				showErrorMessage = true; | ||||
| 			} | ||||
| 		} | ||||
| 		for(const TModID & reverseConflict : resolvedModIDs) | ||||
| 		{ | ||||
| 			if (vstd::contains(allMods.at(reverseConflict).conflicts, brokenModID)) | ||||
| 			{ | ||||
| 				addErrorMessage("vcmi.server.errors.modConflict", brokenModID, reverseConflict); | ||||
| 				showErrorMessage = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// some mods may in a (soft) dependency loop. | ||||
| 		if(!showErrorMessage && brokenMod.config["modType"].String() != "Compatibility") | ||||
| 		{ | ||||
| 			modLoadErrors->appendTextID("vcmi.server.errors.modDependencyLoop"); | ||||
| 			if (allMods.count(brokenModID)) | ||||
| 				modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); | ||||
| 			else | ||||
| 				modLoadErrors->replaceRawString(brokenModID); | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| 	return sortedValidMods; | ||||
| } | ||||
|  | ||||
| std::vector<std::string> CModHandler::getModList(const std::string & path) const | ||||
| { | ||||
| 	std::string modDir = boost::to_upper_copy(path + "MODS/"); | ||||
| 	size_t depth = boost::range::count(modDir, '/'); | ||||
|  | ||||
| 	auto list = CResourceHandler::get("initial")->getFilteredFiles([&](const ResourcePath & id) ->  bool | ||||
| 	{ | ||||
| 		if (id.getType() != EResType::DIRECTORY) | ||||
| 			return false; | ||||
| 		if (!boost::algorithm::starts_with(id.getName(), modDir)) | ||||
| 			return false; | ||||
| 		if (boost::range::count(id.getName(), '/') != depth ) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	}); | ||||
|  | ||||
| 	//storage for found mods | ||||
| 	std::vector<std::string> foundMods; | ||||
| 	for(const auto & entry : list) | ||||
| 	{ | ||||
| 		std::string name = entry.getName(); | ||||
| 		name.erase(0, modDir.size()); //Remove path prefix | ||||
|  | ||||
| 		if (!name.empty()) | ||||
| 			foundMods.push_back(name); | ||||
| 	} | ||||
| 	return foundMods; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| void CModHandler::loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector<TModID> & modsToActivate, bool enableMods) | ||||
| { | ||||
| 	for(const std::string & modName : getModList(path)) | ||||
| 		loadOneMod(modName, parent, modSettings, modsToActivate, enableMods); | ||||
| } | ||||
|  | ||||
| void CModHandler::loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector<TModID> & modsToActivate, bool enableMods) | ||||
| { | ||||
| 	boost::to_lower(modName); | ||||
| 	std::string modFullName = parent.empty() ? modName : parent + '.' + modName; | ||||
|  | ||||
| 	if ( ModScope::isScopeReserved(modFullName)) | ||||
| 	{ | ||||
| 		logMod->error("Can not load mod %s - this name is reserved for internal use!", modFullName); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName))) | ||||
| 	{ | ||||
| 		bool thisModActive = vstd::contains(modsToActivate, modFullName); | ||||
| 		CModInfo mod(modFullName, modSettings[modName], JsonNode(CModInfo::getModFile(modFullName)), thisModActive); | ||||
| 		if (!parent.empty()) // this is submod, add parent to dependencies | ||||
| 			mod.dependencies.insert(parent); | ||||
|  | ||||
| 		allMods[modFullName] = mod; | ||||
| 		if (mod.isEnabled() && enableMods) | ||||
| 			activeMods.push_back(modFullName); | ||||
|  | ||||
| 		loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], modsToActivate, enableMods && mod.isEnabled()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CModHandler::loadMods() | ||||
| { | ||||
| 	JsonNode modConfig; | ||||
|  | ||||
| 	modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); | ||||
| 	const JsonNode & modSettings = modConfig["activeMods"]; | ||||
| 	const std::string & currentPresetName = modConfig["activePreset"].String(); | ||||
| 	const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; | ||||
| 	const JsonNode & modsToActivateJson = currentPreset["mods"]; | ||||
| 	std::vector<TModID> modsToActivate = modsToActivateJson.convertTo<std::vector<TModID>>(); | ||||
|  | ||||
| 	for(const auto & settings : currentPreset["settings"].Struct()) | ||||
| 	{ | ||||
| 		if (!vstd::contains(modsToActivate, settings.first)) | ||||
| 			continue; // settings for inactive mod | ||||
|  | ||||
| 		for (const auto & submod : settings.second.Struct()) | ||||
| 		{ | ||||
| 			if (submod.second.Bool()) | ||||
| 				modsToActivate.push_back(settings.first + '.' + submod.first); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	loadMods("", "", modSettings, modsToActivate, true); | ||||
|  | ||||
| 	coreMod = std::make_unique<CModInfo>(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json")), true); | ||||
| } | ||||
|  | ||||
| std::vector<std::string> CModHandler::getAllMods() const | ||||
| { | ||||
| 	std::vector<std::string> modlist; | ||||
| 	modlist.reserve(allMods.size()); | ||||
| 	for (auto & entry : allMods) | ||||
| 		modlist.push_back(entry.first); | ||||
| 	return modlist; | ||||
| 	return modManager->getActiveMods();// TODO: currently identical to active | ||||
| } | ||||
|  | ||||
| std::vector<std::string> CModHandler::getActiveMods() const | ||||
| { | ||||
| 	return activeMods; | ||||
| 	return modManager->getActiveMods(); | ||||
| } | ||||
|  | ||||
| std::string CModHandler::getModLoadErrors() const | ||||
| { | ||||
| 	return modLoadErrors->toString(); | ||||
| 	return ""; // TODO: modLoadErrors->toString(); | ||||
| } | ||||
|  | ||||
| const CModInfo & CModHandler::getModInfo(const TModID & modId) const | ||||
| const ModDescription & CModHandler::getModInfo(const TModID & modId) const | ||||
| { | ||||
| 	return allMods.at(modId); | ||||
| 	return modManager->getModDescription(modId); | ||||
| } | ||||
|  | ||||
| static JsonNode genDefaultFS() | ||||
| @@ -334,86 +67,95 @@ static JsonNode genDefaultFS() | ||||
| 	return defaultFS; | ||||
| } | ||||
|  | ||||
| static std::string getModDirectory(const TModID & modName) | ||||
| { | ||||
| 	std::string result = modName; | ||||
| 	boost::to_upper(result); | ||||
| 	boost::algorithm::replace_all(result, ".", "/MODS/"); | ||||
| 	return "MODS/" + result; | ||||
| } | ||||
|  | ||||
| static ISimpleResourceLoader * genModFilesystem(const std::string & modName, const JsonNode & conf) | ||||
| { | ||||
| 	static const JsonNode defaultFS = genDefaultFS(); | ||||
|  | ||||
| 	if (!conf["filesystem"].isNull()) | ||||
| 		return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), conf["filesystem"]); | ||||
| 	if (!conf.isNull()) | ||||
| 		return CResourceHandler::createFileSystem(getModDirectory(modName), conf); | ||||
| 	else | ||||
| 		return CResourceHandler::createFileSystem(CModInfo::getModDir(modName), defaultFS); | ||||
| 		return CResourceHandler::createFileSystem(getModDirectory(modName), defaultFS); | ||||
| } | ||||
|  | ||||
| static ui32 calculateModChecksum(const std::string & modName, ISimpleResourceLoader * filesystem) | ||||
| { | ||||
| 	boost::crc_32_type modChecksum; | ||||
| 	// first - add current VCMI version into checksum to force re-validation on VCMI updates | ||||
| 	modChecksum.process_bytes(reinterpret_cast<const void*>(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size()); | ||||
|  | ||||
| 	// second - add mod.json into checksum because filesystem does not contains this file | ||||
| 	// FIXME: remove workaround for core mod | ||||
| 	if (modName != ModScope::scopeBuiltin()) | ||||
| 	{ | ||||
| 		auto modConfFile = CModInfo::getModFile(modName); | ||||
| 		ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32(); | ||||
| 		modChecksum.process_bytes(reinterpret_cast<const void *>(&configChecksum), sizeof(configChecksum)); | ||||
| 	} | ||||
| 	// third - add all detected text files from this mod into checksum | ||||
| 	auto files = filesystem->getFilteredFiles([](const ResourcePath & resID) | ||||
| 	{ | ||||
| 		return (resID.getType() == EResType::TEXT || resID.getType() == EResType::JSON) && | ||||
| 			   ( boost::starts_with(resID.getName(), "DATA") || boost::starts_with(resID.getName(), "CONFIG")); | ||||
| 	}); | ||||
|  | ||||
| 	for (const ResourcePath & file : files) | ||||
| 	{ | ||||
| 		ui32 fileChecksum = filesystem->load(file)->calculateCRC32(); | ||||
| 		modChecksum.process_bytes(reinterpret_cast<const void *>(&fileChecksum), sizeof(fileChecksum)); | ||||
| 	} | ||||
| 	return modChecksum.checksum(); | ||||
| } | ||||
| //static ui32 calculateModChecksum(const std::string & modName, ISimpleResourceLoader * filesystem) | ||||
| //{ | ||||
| //	boost::crc_32_type modChecksum; | ||||
| //	// first - add current VCMI version into checksum to force re-validation on VCMI updates | ||||
| //	modChecksum.process_bytes(reinterpret_cast<const void*>(GameConstants::VCMI_VERSION.data()), GameConstants::VCMI_VERSION.size()); | ||||
| // | ||||
| //	// second - add mod.json into checksum because filesystem does not contains this file | ||||
| //	// FIXME: remove workaround for core mod | ||||
| //	if (modName != ModScope::scopeBuiltin()) | ||||
| //	{ | ||||
| //		auto modConfFile = CModInfo::getModFile(modName); | ||||
| //		ui32 configChecksum = CResourceHandler::get("initial")->load(modConfFile)->calculateCRC32(); | ||||
| //		modChecksum.process_bytes(reinterpret_cast<const void *>(&configChecksum), sizeof(configChecksum)); | ||||
| //	} | ||||
| //	// third - add all detected text files from this mod into checksum | ||||
| //	auto files = filesystem->getFilteredFiles([](const ResourcePath & resID) | ||||
| //	{ | ||||
| //		return (resID.getType() == EResType::TEXT || resID.getType() == EResType::JSON) && | ||||
| //			   ( boost::starts_with(resID.getName(), "DATA") || boost::starts_with(resID.getName(), "CONFIG")); | ||||
| //	}); | ||||
| // | ||||
| //	for (const ResourcePath & file : files) | ||||
| //	{ | ||||
| //		ui32 fileChecksum = filesystem->load(file)->calculateCRC32(); | ||||
| //		modChecksum.process_bytes(reinterpret_cast<const void *>(&fileChecksum), sizeof(fileChecksum)); | ||||
| //	} | ||||
| //	return modChecksum.checksum(); | ||||
| //} | ||||
|  | ||||
| void CModHandler::loadModFilesystems() | ||||
| { | ||||
| 	CGeneralTextHandler::detectInstallParameters(); | ||||
|  | ||||
| 	activeMods = validateAndSortDependencies(activeMods); | ||||
| 	const auto & activeMods = modManager->getActiveMods(); | ||||
|  | ||||
| 	coreMod->updateChecksum(calculateModChecksum(ModScope::scopeBuiltin(), CResourceHandler::get(ModScope::scopeBuiltin()))); | ||||
| 	std::map<TModID, ISimpleResourceLoader *> modFilesystems; | ||||
|  | ||||
| 	std::map<std::string, ISimpleResourceLoader *> modFilesystems; | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 		modFilesystems[modName] = genModFilesystem(modName, getModInfo(modName).getFilesystemConfig()); | ||||
|  | ||||
| 	for(std::string & modName : activeMods) | ||||
| 		modFilesystems[modName] = genModFilesystem(modName, allMods[modName].config); | ||||
|  | ||||
| 	for(std::string & modName : activeMods) | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 		CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]); | ||||
|  | ||||
| 	if (settings["mods"]["validation"].String() == "full") | ||||
| 		checkModFilesystemsConflicts(modFilesystems); | ||||
| } | ||||
|  | ||||
| void CModHandler::checkModFilesystemsConflicts(const std::map<TModID, ISimpleResourceLoader *> & modFilesystems) | ||||
| { | ||||
| 	for(const auto & [leftName, leftFilesystem] : modFilesystems) | ||||
| 	{ | ||||
| 		for(std::string & leftModName : activeMods) | ||||
| 		for(const auto & [rightName, rightFilesystem] : modFilesystems) | ||||
| 		{ | ||||
| 			for(std::string & rightModName : activeMods) | ||||
| 			if (leftName == rightName) | ||||
| 				continue; | ||||
|  | ||||
| 			if (getModDependencies(leftName).count(rightName) || getModDependencies(rightName).count(leftName)) | ||||
| 				continue; | ||||
|  | ||||
| 			if (getModSoftDependencies(leftName).count(rightName) || getModSoftDependencies(rightName).count(leftName)) | ||||
| 				continue; | ||||
|  | ||||
| 			const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY && path.getType() != EResType::JSON;}; | ||||
|  | ||||
| 			std::unordered_set<ResourcePath> leftResources = leftFilesystem->getFilteredFiles(filter); | ||||
| 			std::unordered_set<ResourcePath> rightResources = rightFilesystem->getFilteredFiles(filter); | ||||
|  | ||||
| 			for (auto const & leftFile : leftResources) | ||||
| 			{ | ||||
| 				if (leftModName == rightModName) | ||||
| 					continue; | ||||
|  | ||||
| 				if (getModDependencies(leftModName).count(rightModName) || getModDependencies(rightModName).count(leftModName)) | ||||
| 					continue; | ||||
|  | ||||
| 				if (getModSoftDependencies(leftModName).count(rightModName) || getModSoftDependencies(rightModName).count(leftModName)) | ||||
| 					continue; | ||||
|  | ||||
| 				const auto & filter = [](const ResourcePath &path){return path.getType() != EResType::DIRECTORY && path.getType() != EResType::JSON;}; | ||||
|  | ||||
| 				std::unordered_set<ResourcePath> leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); | ||||
| 				std::unordered_set<ResourcePath> rightResources = modFilesystems[rightModName]->getFilteredFiles(filter); | ||||
|  | ||||
| 				for (auto const & leftFile : leftResources) | ||||
| 				{ | ||||
| 					if (rightResources.count(leftFile)) | ||||
| 						logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftModName, rightModName, leftFile.getOriginalName()); | ||||
| 				} | ||||
| 				if (rightResources.count(leftFile)) | ||||
| 					logMod->warn("Potential confict detected between '%s' and '%s': both mods add file '%s'", leftName, rightName, leftFile.getOriginalName()); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -423,7 +165,8 @@ TModID CModHandler::findResourceOrigin(const ResourcePath & name) const | ||||
| { | ||||
| 	try | ||||
| 	{ | ||||
| 		for(const auto & modID : boost::adaptors::reverse(activeMods)) | ||||
| 		auto activeMode = modManager->getActiveMods(); | ||||
| 		for(const auto & modID : boost::adaptors::reverse(activeMode)) | ||||
| 		{ | ||||
| 			if(CResourceHandler::get(modID)->existsResource(name)) | ||||
| 				return modID; | ||||
| @@ -478,7 +221,7 @@ std::string CModHandler::getModLanguage(const TModID& modId) const | ||||
| 		return VLC->generaltexth->getInstalledLanguage(); | ||||
| 	if(modId == "map") | ||||
| 		return VLC->generaltexth->getPreferredLanguage(); | ||||
| 	return allMods.at(modId).baseLanguage; | ||||
| 	return getModInfo(modId).getBaseLanguage(); | ||||
| } | ||||
|  | ||||
| std::set<TModID> CModHandler::getModDependencies(const TModID & modId) const | ||||
| @@ -489,11 +232,9 @@ std::set<TModID> CModHandler::getModDependencies(const TModID & modId) const | ||||
|  | ||||
| std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & isModFound) const | ||||
| { | ||||
| 	auto it = allMods.find(modId); | ||||
| 	isModFound = (it != allMods.end()); | ||||
|  | ||||
| 	if(isModFound) | ||||
| 		return it->second.dependencies; | ||||
| 	isModFound = modManager->isModActive(modId); | ||||
| 	if (isModFound) | ||||
| 		return modManager->getModDescription(modId).getDependencies(); | ||||
|  | ||||
| 	logMod->error("Mod not found: '%s'", modId); | ||||
| 	return {}; | ||||
| @@ -501,54 +242,37 @@ std::set<TModID> CModHandler::getModDependencies(const TModID & modId, bool & is | ||||
|  | ||||
| std::set<TModID> CModHandler::getModSoftDependencies(const TModID & modId) const | ||||
| { | ||||
| 	auto it = allMods.find(modId); | ||||
| 	if(it != allMods.end()) | ||||
| 		return it->second.softDependencies; | ||||
| 	logMod->error("Mod not found: '%s'", modId); | ||||
| 	return {}; | ||||
| 	return modManager->getModDescription(modId).getSoftDependencies(); | ||||
| } | ||||
|  | ||||
| std::set<TModID> CModHandler::getModEnabledSoftDependencies(const TModID & modId) const | ||||
| { | ||||
| 	std::set<TModID> softDependencies = getModSoftDependencies(modId); | ||||
| 	for (auto it = softDependencies.begin(); it != softDependencies.end();) | ||||
| 	{ | ||||
| 		if (allMods.find(*it) == allMods.end()) | ||||
| 			it = softDependencies.erase(it); | ||||
| 		else | ||||
| 			it++; | ||||
| 	} | ||||
|  | ||||
| 	vstd::erase_if(softDependencies, [&](const TModID & dependency){ return !modManager->isModActive(dependency);}); | ||||
|  | ||||
| 	return softDependencies; | ||||
| } | ||||
|  | ||||
| void CModHandler::initializeConfig() | ||||
| { | ||||
| 	VLC->settingsHandler->loadBase(JsonUtils::assembleFromFiles(coreMod->config["settings"])); | ||||
|  | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 	for(const TModID & modName : getActiveMods()) | ||||
| 	{ | ||||
| 		const auto & mod = allMods[modName]; | ||||
| 		if (!mod.config["settings"].isNull()) | ||||
| 			VLC->settingsHandler->loadBase(mod.config["settings"]); | ||||
| 		const auto & mod = getModInfo(modName); | ||||
| 		if (!mod.getConfig()["settings"].isNull()) | ||||
| 			VLC->settingsHandler->loadBase(mod.getConfig()["settings"]); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| CModVersion CModHandler::getModVersion(TModID modName) const | ||||
| { | ||||
| 	if (allMods.count(modName)) | ||||
| 		return allMods.at(modName).getVerificationInfo().version; | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| void CModHandler::loadTranslation(const TModID & modName) | ||||
| { | ||||
| 	const auto & mod = allMods[modName]; | ||||
| 	const auto & mod = getModInfo(modName); | ||||
|  | ||||
| 	std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage(); | ||||
| 	std::string modBaseLanguage = allMods[modName].baseLanguage; | ||||
| 	std::string modBaseLanguage = getModInfo(modName).getBaseLanguage(); | ||||
|  | ||||
| 	JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.config["translations"]); | ||||
| 	JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.config[preferredLanguage]["translations"]); | ||||
| 	JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.getConfig()["translations"]); | ||||
| 	JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.getConfig()[preferredLanguage]["translations"]); | ||||
|  | ||||
| 	VLC->generaltexth->loadTranslationOverrides(modName, modBaseLanguage, baseTranslation); | ||||
| 	VLC->generaltexth->loadTranslationOverrides(modName, preferredLanguage, extraTranslation); | ||||
| @@ -556,29 +280,22 @@ void CModHandler::loadTranslation(const TModID & modName) | ||||
|  | ||||
| void CModHandler::load() | ||||
| { | ||||
| 	CStopWatch totalTime; | ||||
| 	CStopWatch timer; | ||||
|  | ||||
| 	logMod->info("\tInitializing content handler: %d ms", timer.getDiff()); | ||||
| 	logMod->info("\tInitializing content handler"); | ||||
|  | ||||
| 	content->init(); | ||||
|  | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 	{ | ||||
| 		logMod->trace("Generating checksum for %s", modName); | ||||
| 		allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName))); | ||||
| 	} | ||||
| //	for(const TModID & modName : getActiveMods()) | ||||
| //	{ | ||||
| //		logMod->trace("Generating checksum for %s", modName); | ||||
| //		allMods[modName].updateChecksum(calculateModChecksum(modName, CResourceHandler::get(modName))); | ||||
| //	} | ||||
|  | ||||
| 	// first - load virtual builtin mod that contains all data | ||||
| 	// TODO? move all data into real mods? RoE, AB, SoD, WoG | ||||
| 	content->preloadData(*coreMod); | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 		content->preloadData(allMods[modName]); | ||||
| 	logMod->info("\tParsing mod data: %d ms", timer.getDiff()); | ||||
| 	for(const TModID & modName : getActiveMods()) | ||||
| 		content->preloadData(getModInfo(modName)); | ||||
| 	logMod->info("\tParsing mod data"); | ||||
|  | ||||
| 	content->load(*coreMod); | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 		content->load(allMods[modName]); | ||||
| 	for(const TModID & modName : getActiveMods()) | ||||
| 		content->load(getModInfo(modName)); | ||||
|  | ||||
| #if SCRIPTING_ENABLED | ||||
| 	VLC->scriptHandler->performRegistration(VLC);//todo: this should be done before any other handlers load | ||||
| @@ -586,36 +303,36 @@ void CModHandler::load() | ||||
|  | ||||
| 	content->loadCustom(); | ||||
|  | ||||
| 	for(const TModID & modName : activeMods) | ||||
| 	for(const TModID & modName : getActiveMods()) | ||||
| 		loadTranslation(modName); | ||||
|  | ||||
| 	logMod->info("\tLoading mod data: %d ms", timer.getDiff()); | ||||
| 	logMod->info("\tLoading mod data"); | ||||
| 	VLC->creh->loadCrExpMod(); | ||||
| 	VLC->identifiersHandler->finalize(); | ||||
| 	logMod->info("\tResolving identifiers: %d ms", timer.getDiff()); | ||||
| 	logMod->info("\tResolving identifiers"); | ||||
|  | ||||
| 	content->afterLoadFinalization(); | ||||
| 	logMod->info("\tHandlers post-load finalization: %d ms ", timer.getDiff()); | ||||
| 	logMod->info("\tAll game content loaded in %d ms", totalTime.getDiff()); | ||||
| 	logMod->info("\tHandlers post-load finalization"); | ||||
| 	logMod->info("\tAll game content loaded"); | ||||
| } | ||||
|  | ||||
| void CModHandler::afterLoad(bool onlyEssential) | ||||
| { | ||||
| 	JsonNode modSettings; | ||||
| 	for (auto & modEntry : allMods) | ||||
| 	{ | ||||
| 		std::string pointer = "/" + boost::algorithm::replace_all_copy(modEntry.first, ".", "/mods/"); | ||||
| 	//JsonNode modSettings; | ||||
| 	//for (auto & modEntry : getActiveMods()) | ||||
| 	//{ | ||||
| 	//	std::string pointer = "/" + boost::algorithm::replace_all_copy(modEntry.first, ".", "/mods/"); | ||||
|  | ||||
| 		modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData(); | ||||
| 	} | ||||
| 	modSettings[ModScope::scopeBuiltin()] = coreMod->saveLocalData(); | ||||
| 	modSettings[ModScope::scopeBuiltin()]["name"].String() = "Original game files"; | ||||
| 	//	modSettings["activeMods"].resolvePointer(pointer) = modEntry.second.saveLocalData(); | ||||
| 	//} | ||||
| 	//modSettings[ModScope::scopeBuiltin()] = coreMod->saveLocalData(); | ||||
| 	//modSettings[ModScope::scopeBuiltin()]["name"].String() = "Original game files"; | ||||
|  | ||||
| 	if(!onlyEssential) | ||||
| 	{ | ||||
| 		std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); | ||||
| 		file << modSettings.toString(); | ||||
| 	} | ||||
| 	//if(!onlyEssential) | ||||
| 	//{ | ||||
| 	//	std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); | ||||
| 	//	file << modSettings.toString(); | ||||
| 	//} | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -12,51 +12,26 @@ | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| class CModHandler; | ||||
| class CModIdentifier; | ||||
| class CModInfo; | ||||
| struct CModVersion; | ||||
| class JsonNode; | ||||
| class IHandlerBase; | ||||
| class CIdentifierStorage; | ||||
| class ModDescription; | ||||
| class CContentHandler; | ||||
| struct ModVerificationInfo; | ||||
| class ResourcePath; | ||||
| class MetaString; | ||||
| class ModManager; | ||||
| class ISimpleResourceLoader; | ||||
|  | ||||
| using TModID = std::string; | ||||
|  | ||||
| class DLL_LINKAGE CModHandler final : boost::noncopyable | ||||
| { | ||||
| 	std::map <TModID, CModInfo> allMods; | ||||
| 	std::vector <TModID> activeMods;//active mods, in order in which they were loaded | ||||
| 	std::unique_ptr<CModInfo> coreMod; | ||||
| 	mutable std::unique_ptr<MetaString> modLoadErrors; | ||||
| 	std::unique_ptr<ModManager> modManager; | ||||
|  | ||||
| 	bool hasCircularDependency(const TModID & mod, std::set<TModID> currentList = std::set<TModID>()) const; | ||||
|  | ||||
| 	/** | ||||
| 	* 1. Set apart mods with resolved dependencies from mods which have unresolved dependencies | ||||
| 	* 2. Sort resolved mods using topological algorithm | ||||
| 	* 3. Log all problem mods and their unresolved dependencies | ||||
| 	* | ||||
| 	* @param modsToResolve list of valid mod IDs (checkDependencies returned true - TODO: Clarify it.) | ||||
| 	* @return a vector of the topologically sorted resolved mods: child nodes (dependent mods) have greater index than parents | ||||
| 	*/ | ||||
| 	std::vector<TModID> validateAndSortDependencies(std::vector <TModID> modsToResolve) const; | ||||
|  | ||||
| 	std::vector<std::string> getModList(const std::string & path) const; | ||||
| 	void loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector<TModID> & modsToActivate, bool enableMods); | ||||
| 	void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector<TModID> & modsToActivate, bool enableMods); | ||||
| 	void loadTranslation(const TModID & modName); | ||||
|  | ||||
| 	CModVersion getModVersion(TModID modName) const; | ||||
| 	void checkModFilesystemsConflicts(const std::map<TModID, ISimpleResourceLoader *> & modFilesystems); | ||||
|  | ||||
| public: | ||||
| 	std::shared_ptr<CContentHandler> content; //(!)Do not serialize FIXME: make private | ||||
| 	std::shared_ptr<CContentHandler> content; //FIXME: make private | ||||
|  | ||||
| 	/// receives list of available mods and trying to load mod.json from all of them | ||||
| 	void initializeConfig(); | ||||
| 	void loadMods(); | ||||
| 	void loadModFilesystems(); | ||||
|  | ||||
| 	/// returns ID of mod that provides selected file resource | ||||
| @@ -82,7 +57,7 @@ public: | ||||
| 	/// Returns human-readable string that describes errors encounter during mod loading, such as missing dependencies | ||||
| 	std::string getModLoadErrors() const; | ||||
| 	 | ||||
| 	const CModInfo & getModInfo(const TModID & modId) const; | ||||
| 	const ModDescription & getModInfo(const TModID & modId) const; | ||||
|  | ||||
| 	/// load content from all available mods | ||||
| 	void load(); | ||||
|   | ||||
| @@ -1,204 +0,0 @@ | ||||
| /* | ||||
|  * CModInfo.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "CModInfo.h" | ||||
|  | ||||
| #include "../texts/CGeneralTextHandler.h" | ||||
| #include "../VCMI_Lib.h" | ||||
| #include "../filesystem/Filesystem.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| static JsonNode addMeta(JsonNode config, const std::string & meta) | ||||
| { | ||||
| 	config.setModScope(meta); | ||||
| 	return config; | ||||
| } | ||||
|  | ||||
| std::set<TModID> CModInfo::readModList(const JsonNode & input) | ||||
| { | ||||
| 	std::set<TModID> result; | ||||
|  | ||||
| 	for (auto const & string : input.convertTo<std::set<std::string>>()) | ||||
| 		result.insert(boost::to_lower_copy(string)); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| CModInfo::CModInfo(): | ||||
| 	explicitlyEnabled(false), | ||||
| 	implicitlyEnabled(true), | ||||
| 	validation(PENDING) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive): | ||||
| 	identifier(identifier), | ||||
| 	dependencies(readModList(config["depends"])), | ||||
| 	softDependencies(readModList(config["softDepends"])), | ||||
| 	conflicts(readModList(config["conflicts"])), | ||||
| 	explicitlyEnabled(isActive), | ||||
| 	implicitlyEnabled(true), | ||||
| 	validation(PENDING), | ||||
| 	config(addMeta(config, identifier)) | ||||
| { | ||||
| 	if (!config["name"].String().empty()) | ||||
| 		verificationInfo.name = config["name"].String(); | ||||
| 	else | ||||
| 		verificationInfo.name = identifier; | ||||
|  | ||||
| 	verificationInfo.version = CModVersion::fromString(config["version"].String()); | ||||
| 	verificationInfo.parent = identifier.substr(0, identifier.find_last_of('.')); | ||||
| 	if(verificationInfo.parent == identifier) | ||||
| 		verificationInfo.parent.clear(); | ||||
|  | ||||
| 	if(!config["compatibility"].isNull()) | ||||
| 	{ | ||||
| 		vcmiCompatibleMin = CModVersion::fromString(config["compatibility"]["min"].String()); | ||||
| 		vcmiCompatibleMax = CModVersion::fromString(config["compatibility"]["max"].String()); | ||||
| 	} | ||||
|  | ||||
| 	if (!config["language"].isNull()) | ||||
| 		baseLanguage = config["language"].String(); | ||||
| 	else | ||||
| 		baseLanguage = "english"; | ||||
|  | ||||
| 	loadLocalData(local); | ||||
| } | ||||
|  | ||||
| JsonNode CModInfo::saveLocalData() const | ||||
| { | ||||
| 	std::ostringstream stream; | ||||
| 	stream << std::noshowbase << std::hex << std::setw(8) << std::setfill('0') << verificationInfo.checksum; | ||||
|  | ||||
| 	JsonNode conf; | ||||
| 	conf["active"].Bool() = explicitlyEnabled; | ||||
| 	conf["validated"].Bool() = validation != FAILED; | ||||
| 	conf["checksum"].String() = stream.str(); | ||||
| 	return conf; | ||||
| } | ||||
|  | ||||
| std::string CModInfo::getModDir(const std::string & name) | ||||
| { | ||||
| 	return "MODS/" + boost::algorithm::replace_all_copy(name, ".", "/MODS/"); | ||||
| } | ||||
|  | ||||
| JsonPath CModInfo::getModFile(const std::string & name) | ||||
| { | ||||
| 	return JsonPath::builtinTODO(getModDir(name) + "/mod.json"); | ||||
| } | ||||
|  | ||||
| void CModInfo::updateChecksum(ui32 newChecksum) | ||||
| { | ||||
| 	// comment-out next line to force validation of all mods ignoring checksum | ||||
| 	if (newChecksum != verificationInfo.checksum) | ||||
| 	{ | ||||
| 		verificationInfo.checksum = newChecksum; | ||||
| 		validation = PENDING; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CModInfo::loadLocalData(const JsonNode & data) | ||||
| { | ||||
| 	bool validated = false; | ||||
| 	implicitlyEnabled = true; | ||||
| 	verificationInfo.checksum = 0; | ||||
| 	if (data.isStruct()) | ||||
| 	{ | ||||
| 		validated = data["validated"].Bool(); | ||||
| 		updateChecksum(strtol(data["checksum"].String().c_str(), nullptr, 16)); | ||||
| 	} | ||||
|  | ||||
| 	//check compatibility | ||||
| 	implicitlyEnabled &= (vcmiCompatibleMin.isNull() || CModVersion::GameVersion().compatible(vcmiCompatibleMin, true, true)); | ||||
| 	implicitlyEnabled &= (vcmiCompatibleMax.isNull() || vcmiCompatibleMax.compatible(CModVersion::GameVersion(), true, true)); | ||||
|  | ||||
| 	if(!implicitlyEnabled) | ||||
| 		logGlobal->warn("Mod %s is incompatible with current version of VCMI and cannot be enabled", verificationInfo.name); | ||||
|  | ||||
| 	if (config["modType"].String() == "Translation") | ||||
| 	{ | ||||
| 		if (baseLanguage != CGeneralTextHandler::getPreferredLanguage()) | ||||
| 		{ | ||||
| 			if (identifier.find_last_of('.') == std::string::npos) | ||||
| 				logGlobal->warn("Translation mod %s was not loaded: language mismatch!", verificationInfo.name); | ||||
| 			implicitlyEnabled = false; | ||||
| 		} | ||||
| 	} | ||||
| 	if (config["modType"].String() == "Compatibility") | ||||
| 	{ | ||||
| 		// compatibility mods are always explicitly enabled | ||||
| 		// however they may be implicitly disabled - if one of their dependencies is missing | ||||
| 		explicitlyEnabled = true; | ||||
| 	} | ||||
|  | ||||
| 	if (isEnabled()) | ||||
| 		validation = validated ? PASSED : PENDING; | ||||
| 	else | ||||
| 		validation = validated ? PASSED : FAILED; | ||||
|  | ||||
| 	verificationInfo.impactsGameplay = checkModGameplayAffecting(); | ||||
| } | ||||
|  | ||||
| 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" | ||||
| 	}; | ||||
|  | ||||
| 	JsonPath modFileResource(CModInfo::getModFile(identifier)); | ||||
|  | ||||
| 	if(CResourceHandler::get("initial")->existsResource(modFileResource)) | ||||
| 	{ | ||||
| 		const JsonNode modConfig(modFileResource); | ||||
|  | ||||
| 		for(const auto & key : keysToTest) | ||||
| 		{ | ||||
| 			if (!modConfig[key].isNull()) | ||||
| 			{ | ||||
| 				modGameplayAffecting = true; | ||||
| 				return *modGameplayAffecting; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	modGameplayAffecting = false; | ||||
| 	return *modGameplayAffecting; | ||||
| } | ||||
|  | ||||
| const ModVerificationInfo & CModInfo::getVerificationInfo() const | ||||
| { | ||||
| 	assert(!verificationInfo.name.empty()); | ||||
| 	return verificationInfo; | ||||
| } | ||||
|  | ||||
| bool CModInfo::isEnabled() const | ||||
| { | ||||
| 	return implicitlyEnabled && explicitlyEnabled; | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| @@ -1,85 +0,0 @@ | ||||
| /* | ||||
|  * CModInfo.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../json/JsonNode.h" | ||||
| #include "ModVerificationInfo.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| 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; | ||||
|  | ||||
| 	static std::set<TModID> readModList(const JsonNode & input); | ||||
| public: | ||||
| 	enum EValidationStatus | ||||
| 	{ | ||||
| 		PENDING, | ||||
| 		FAILED, | ||||
| 		PASSED | ||||
| 	}; | ||||
| 	 | ||||
| 	/// identifier, identical to name of folder with mod | ||||
| 	std::string identifier; | ||||
|  | ||||
| 	/// detailed mod description | ||||
| 	std::string description; | ||||
|  | ||||
| 	/// Base language of mod, all mod strings are assumed to be in this language | ||||
| 	std::string baseLanguage; | ||||
|  | ||||
| 	/// vcmi versions compatible with the mod | ||||
| 	CModVersion vcmiCompatibleMin, vcmiCompatibleMax; | ||||
|  | ||||
| 	/// list of mods that should be loaded before this one | ||||
| 	std::set <TModID> dependencies; | ||||
|  | ||||
| 	/// list of mods if they are enabled, should be loaded before this one. this mod will overwrite any conflicting items from its soft dependency mods | ||||
| 	std::set <TModID> softDependencies; | ||||
|  | ||||
| 	/// list of mods that can't be used in the same time as this one | ||||
| 	std::set <TModID> conflicts; | ||||
|  | ||||
| 	EValidationStatus validation; | ||||
|  | ||||
| 	JsonNode config; | ||||
|  | ||||
| 	CModInfo(); | ||||
| 	CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive); | ||||
|  | ||||
| 	JsonNode saveLocalData() const; | ||||
| 	void updateChecksum(ui32 newChecksum); | ||||
|  | ||||
| 	bool isEnabled() const; | ||||
|  | ||||
| 	static std::string getModDir(const std::string & name); | ||||
| 	static JsonPath getModFile(const std::string & name); | ||||
|  | ||||
| 	/// return true if this mod can affect gameplay, e.g. adds or modifies any game objects | ||||
| 	bool checkModGameplayAffecting() const; | ||||
| 	 | ||||
| 	const ModVerificationInfo & getVerificationInfo() const; | ||||
|  | ||||
| private: | ||||
| 	/// true if mod is enabled by user, e.g. in Launcher UI | ||||
| 	bool explicitlyEnabled; | ||||
|  | ||||
| 	/// true if mod can be loaded - compatible and has no missing deps | ||||
| 	bool implicitlyEnabled; | ||||
| 	 | ||||
| 	ModVerificationInfo verificationInfo; | ||||
|  | ||||
| 	void loadLocalData(const JsonNode & data); | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| @@ -11,7 +11,8 @@ | ||||
| #include "ContentTypeHandler.h" | ||||
|  | ||||
| #include "CModHandler.h" | ||||
| #include "CModInfo.h" | ||||
| #include "ModDescription.h" | ||||
| #include "ModManager.h" | ||||
| #include "ModScope.h" | ||||
|  | ||||
| #include "../BattleFieldHandler.h" | ||||
| @@ -294,39 +295,43 @@ void CContentHandler::afterLoadFinalization() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CContentHandler::preloadData(CModInfo & mod) | ||||
| void CContentHandler::preloadData(const ModDescription & mod) | ||||
| { | ||||
| 	bool validate = validateMod(mod); | ||||
| 	preloadModData(mod.getID(), mod.getConfig(), false); | ||||
|  | ||||
| 	// print message in format [<8-symbols checksum>] <modname> | ||||
| 	auto & info = mod.getVerificationInfo(); | ||||
| 	logMod->info("\t\t[%08x]%s", info.checksum, info.name); | ||||
|  | ||||
| 	if (validate && mod.identifier != ModScope::scopeBuiltin()) | ||||
| 	{ | ||||
| 		if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier)) | ||||
| 			mod.validation = CModInfo::FAILED; | ||||
| 	} | ||||
| 	if (!preloadModData(mod.identifier, mod.config, validate)) | ||||
| 		mod.validation = CModInfo::FAILED; | ||||
| //	bool validate = validateMod(mod); | ||||
| // | ||||
| //	// print message in format [<8-symbols checksum>] <modname> | ||||
| //	auto & info = mod.getVerificationInfo(); | ||||
| //	logMod->info("\t\t[%08x]%s", info.checksum, info.name); | ||||
| // | ||||
| //	if (validate && mod.identifier != ModScope::scopeBuiltin()) | ||||
| //	{ | ||||
| //		if (!JsonUtils::validate(mod.config, "vcmi:mod", mod.identifier)) | ||||
| //			mod.validation = CModInfo::FAILED; | ||||
| //	} | ||||
| //	if (!preloadModData(mod.identifier, mod.config, validate)) | ||||
| //		mod.validation = CModInfo::FAILED; | ||||
| } | ||||
|  | ||||
| void CContentHandler::load(CModInfo & mod) | ||||
| void CContentHandler::load(const ModDescription & mod) | ||||
| { | ||||
| 	bool validate = validateMod(mod); | ||||
| 	loadMod(mod.getID(), false); | ||||
|  | ||||
| 	if (!loadMod(mod.identifier, validate)) | ||||
| 		mod.validation = CModInfo::FAILED; | ||||
|  | ||||
| 	if (validate) | ||||
| 	{ | ||||
| 		if (mod.validation != CModInfo::FAILED) | ||||
| 			logMod->info("\t\t[DONE] %s", mod.getVerificationInfo().name); | ||||
| 		else | ||||
| 			logMod->error("\t\t[FAIL] %s", mod.getVerificationInfo().name); | ||||
| 	} | ||||
| 	else | ||||
| 		logMod->info("\t\t[SKIP] %s", mod.getVerificationInfo().name); | ||||
| //	bool validate = validateMod(mod); | ||||
| // | ||||
| //	if (!loadMod(mod.identifier, validate)) | ||||
| //		mod.validation = CModInfo::FAILED; | ||||
| // | ||||
| //	if (validate) | ||||
| //	{ | ||||
| //		if (mod.validation != CModInfo::FAILED) | ||||
| //			logMod->info("\t\t[DONE] %s", mod.getVerificationInfo().name); | ||||
| //		else | ||||
| //			logMod->error("\t\t[FAIL] %s", mod.getVerificationInfo().name); | ||||
| //	} | ||||
| //	else | ||||
| //		logMod->info("\t\t[SKIP] %s", mod.getVerificationInfo().name); | ||||
| } | ||||
|  | ||||
| const ContentTypeHandler & CContentHandler::operator[](const std::string & name) const | ||||
| @@ -334,13 +339,13 @@ const ContentTypeHandler & CContentHandler::operator[](const std::string & name) | ||||
| 	return handlers.at(name); | ||||
| } | ||||
|  | ||||
| bool CContentHandler::validateMod(const CModInfo & mod) const | ||||
| bool CContentHandler::validateMod(const ModDescription & mod) const | ||||
| { | ||||
| 	if (settings["mods"]["validation"].String() == "full") | ||||
| 		return true; | ||||
|  | ||||
| 	if (mod.validation == CModInfo::PASSED) | ||||
| 		return false; | ||||
| //	if (mod.validation == CModInfo::PASSED) | ||||
| //		return false; | ||||
|  | ||||
| 	if (settings["mods"]["validation"].String() == "off") | ||||
| 		return false; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| class IHandlerBase; | ||||
| class CModInfo; | ||||
| class ModDescription; | ||||
|  | ||||
| /// internal type to handle loading of one data type (e.g. artifacts, creatures) | ||||
| class DLL_LINKAGE ContentTypeHandler | ||||
| @@ -58,15 +58,15 @@ class DLL_LINKAGE CContentHandler | ||||
|  | ||||
| 	std::map<std::string, ContentTypeHandler> handlers; | ||||
|  | ||||
| 	bool validateMod(const CModInfo & mod) const; | ||||
| 	bool validateMod(const ModDescription & mod) const; | ||||
| public: | ||||
| 	void init(); | ||||
|  | ||||
| 	/// preloads all data from fileList as data from modName. | ||||
| 	void preloadData(CModInfo & mod); | ||||
| 	void preloadData(const ModDescription & mod); | ||||
|  | ||||
| 	/// actually loads data in mod | ||||
| 	void load(CModInfo & mod); | ||||
| 	void load(const ModDescription & mod); | ||||
|  | ||||
| 	void loadCustom(); | ||||
|  | ||||
|   | ||||
							
								
								
									
										114
									
								
								lib/modding/ModDescription.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								lib/modding/ModDescription.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| /* | ||||
|  * ModDescription.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "ModDescription.h" | ||||
|  | ||||
| #include "CModVersion.h" | ||||
| #include "ModVerificationInfo.h" | ||||
|  | ||||
| #include "../json/JsonNode.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| ModDescription::ModDescription(const TModID & fullID, const JsonNode & config) | ||||
| 	: identifier(fullID) | ||||
| 	, config(std::make_unique<JsonNode>(config)) | ||||
| 	, dependencies(loadModList(config["depends"])) | ||||
| 	, softDependencies(loadModList(config["softDepends"])) | ||||
| 	, conflicts(loadModList(config["conflicts"])) | ||||
| { | ||||
| 	if(getID() != "core") | ||||
| 		dependencies.insert("core"); | ||||
| } | ||||
|  | ||||
| ModDescription::~ModDescription() = default; | ||||
|  | ||||
| TModSet ModDescription::loadModList(const JsonNode & configNode) const | ||||
| { | ||||
| 	TModSet result; | ||||
| 	for(const auto & entry : configNode.Vector()) | ||||
| 		result.insert(boost::algorithm::to_lower_copy(entry.String())); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| const TModID & ModDescription::getID() const | ||||
| { | ||||
| 	return identifier; | ||||
| } | ||||
|  | ||||
| TModID ModDescription::getParentID() const | ||||
| { | ||||
| 	size_t dotPos = identifier.find_last_of('.'); | ||||
|  | ||||
| 	if(dotPos == std::string::npos) | ||||
| 		return {}; | ||||
|  | ||||
| 	return identifier.substr(0, dotPos); | ||||
| } | ||||
|  | ||||
| const TModSet & ModDescription::getDependencies() const | ||||
| { | ||||
| 	return dependencies; | ||||
| } | ||||
|  | ||||
| const TModSet & ModDescription::getSoftDependencies() const | ||||
| { | ||||
| 	return softDependencies; | ||||
| } | ||||
|  | ||||
| const TModSet & ModDescription::getConflicts() const | ||||
| { | ||||
| 	return conflicts; | ||||
| } | ||||
|  | ||||
| const std::string & ModDescription::getBaseLanguage() const | ||||
| { | ||||
| 	static const std::string defaultLanguage = "english"; | ||||
|  | ||||
| 	return getConfig()["language"].isString() ? getConfig()["language"].String() : defaultLanguage; | ||||
| } | ||||
|  | ||||
| const std::string & ModDescription::getName() const | ||||
| { | ||||
| 	return getConfig()["name"].String(); | ||||
| } | ||||
|  | ||||
| const JsonNode & ModDescription::getFilesystemConfig() const | ||||
| { | ||||
| 	return getConfig()["filesystem"]; | ||||
| } | ||||
|  | ||||
| const JsonNode & ModDescription::getConfig() const | ||||
| { | ||||
| 	return *config; | ||||
| } | ||||
|  | ||||
| CModVersion ModDescription::getVersion() const | ||||
| { | ||||
| 	return CModVersion::fromString(getConfig()["version"].String()); | ||||
| } | ||||
|  | ||||
| ModVerificationInfo ModDescription::getVerificationInfo() const | ||||
| { | ||||
| 	ModVerificationInfo result; | ||||
| 	result.name = getName(); | ||||
| 	result.version = getVersion(); | ||||
| 	result.impactsGameplay = affectsGameplay(); | ||||
| 	result.parent = getParentID(); | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| bool ModDescription::affectsGameplay() const | ||||
| { | ||||
| 	return false; // TODO | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										56
									
								
								lib/modding/ModDescription.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								lib/modding/ModDescription.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| /* | ||||
|  * ModDescription.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| struct CModVersion; | ||||
| struct ModVerificationInfo; | ||||
| class JsonNode; | ||||
|  | ||||
| using TModID = std::string; | ||||
| using TModList = std::vector<TModID>; | ||||
| using TModSet = std::set<TModID>; | ||||
|  | ||||
| class DLL_LINKAGE ModDescription : boost::noncopyable | ||||
| { | ||||
| 	TModID identifier; | ||||
| 	TModSet dependencies; | ||||
| 	TModSet softDependencies; | ||||
| 	TModSet conflicts; | ||||
|  | ||||
| 	std::unique_ptr<JsonNode> config; | ||||
|  | ||||
| 	TModSet loadModList(const JsonNode & configNode) const; | ||||
|  | ||||
| public: | ||||
| 	ModDescription(const TModID & fullID, const JsonNode & config); | ||||
| 	~ModDescription(); | ||||
|  | ||||
| 	const TModID & getID() const; | ||||
| 	TModID getParentID() const; | ||||
|  | ||||
| 	const TModSet & getDependencies() const; | ||||
| 	const TModSet & getSoftDependencies() const; | ||||
| 	const TModSet & getConflicts() const; | ||||
|  | ||||
| 	const std::string & getBaseLanguage() const; | ||||
| 	const std::string & getName() const; | ||||
|  | ||||
| 	const JsonNode & getFilesystemConfig() const; | ||||
| 	const JsonNode & getConfig() const; | ||||
|  | ||||
| 	CModVersion getVersion() const; | ||||
| 	ModVerificationInfo getVerificationInfo() const; | ||||
|  | ||||
| 	bool affectsGameplay() const; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										367
									
								
								lib/modding/ModManager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										367
									
								
								lib/modding/ModManager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,367 @@ | ||||
| /* | ||||
|  * ModManager.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "ModManager.h" | ||||
|  | ||||
| #include "ModDescription.h" | ||||
| #include "ModScope.h" | ||||
|  | ||||
| #include "../filesystem/Filesystem.h" | ||||
| #include "../json/JsonNode.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| static std::string getModSettingsDirectory(const TModID & modName) | ||||
| { | ||||
| 	std::string result = modName; | ||||
| 	boost::to_upper(result); | ||||
| 	boost::algorithm::replace_all(result, ".", "/MODS/"); | ||||
| 	return "MODS/" + result + "/MODS/"; | ||||
| } | ||||
|  | ||||
| static JsonPath getModDescriptionFile(const TModID & modName) | ||||
| { | ||||
| 	std::string result = modName; | ||||
| 	boost::to_upper(result); | ||||
| 	boost::algorithm::replace_all(result, ".", "/MODS/"); | ||||
| 	return JsonPath::builtin("MODS/" + result + "/mod"); | ||||
| } | ||||
|  | ||||
| ModsState::ModsState() | ||||
| { | ||||
| 	modList.push_back(ModScope::scopeBuiltin()); | ||||
|  | ||||
| 	std::vector<TModID> testLocations = scanModsDirectory("MODS/"); | ||||
|  | ||||
| 	while(!testLocations.empty()) | ||||
| 	{ | ||||
| 		std::string target = testLocations.back(); | ||||
| 		testLocations.pop_back(); | ||||
| 		modList.push_back(boost::algorithm::to_lower_copy(target)); | ||||
|  | ||||
| 		for(const auto & submod : scanModsDirectory(getModSettingsDirectory(target))) | ||||
| 			testLocations.push_back(target + '.' + submod); | ||||
|  | ||||
| 		// TODO: check that this is vcmi mod and not era mod? | ||||
| 		// TODO: check that mod name is not reserved (ModScope::isScopeReserved(modFullName))) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TModList ModsState::getAllMods() const | ||||
| { | ||||
| 	return modList; | ||||
| } | ||||
|  | ||||
| std::vector<TModID> ModsState::scanModsDirectory(const std::string & modDir) const | ||||
| { | ||||
| 	size_t depth = boost::range::count(modDir, '/'); | ||||
|  | ||||
| 	const auto & modScanFilter = [&](const ResourcePath & id) -> bool | ||||
| 	{ | ||||
| 		if(id.getType() != EResType::DIRECTORY) | ||||
| 			return false; | ||||
| 		if(!boost::algorithm::starts_with(id.getName(), modDir)) | ||||
| 			return false; | ||||
| 		if(boost::range::count(id.getName(), '/') != depth) | ||||
| 			return false; | ||||
| 		return true; | ||||
| 	}; | ||||
|  | ||||
| 	auto list = CResourceHandler::get("initial")->getFilteredFiles(modScanFilter); | ||||
|  | ||||
| 	//storage for found mods | ||||
| 	std::vector<TModID> foundMods; | ||||
| 	for(const auto & entry : list) | ||||
| 	{ | ||||
| 		std::string name = entry.getName(); | ||||
| 		name.erase(0, modDir.size()); //Remove path prefix | ||||
|  | ||||
| 		if(name.empty()) | ||||
| 			continue; | ||||
|  | ||||
| 		if(name.find('.') != std::string::npos) | ||||
| 			continue; | ||||
|  | ||||
| 		if(!CResourceHandler::get("initial")->existsResource(JsonPath::builtin(entry.getName() + "/MOD"))) | ||||
| 			continue; | ||||
|  | ||||
| 		foundMods.push_back(name); | ||||
| 	} | ||||
| 	return foundMods; | ||||
| } | ||||
|  | ||||
| /////////////////////////////////////////////////////////////////////////////// | ||||
|  | ||||
| static JsonNode loadModSettings(const JsonPath & path) | ||||
| { | ||||
| 	if(CResourceHandler::get("local")->existsResource(ResourcePath(path))) | ||||
| 	{ | ||||
| 		return JsonNode(path); | ||||
| 	} | ||||
| 	// Probably new install. Create initial configuration | ||||
| 	CResourceHandler::get("local")->createResource(path.getOriginalName() + ".json"); | ||||
| 	return JsonNode(); | ||||
| } | ||||
|  | ||||
| ModsPresetState::ModsPresetState() | ||||
| { | ||||
| 	modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); | ||||
|  | ||||
| 	if(modConfig["presets"].isNull()) | ||||
| 	{ | ||||
| 		modConfig["activePreset"] = JsonNode("default"); | ||||
| 		if(modConfig["activeMods"].isNull()) | ||||
| 			createInitialPreset(); // new install | ||||
| 		else | ||||
| 			importInitialPreset(); // 1.5 format import | ||||
|  | ||||
| 		saveConfiguration(modConfig); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void ModsPresetState::saveConfiguration(const JsonNode & modSettings) | ||||
| { | ||||
| 	std::fstream file(CResourceHandler::get()->getResourceName(ResourcePath("config/modSettings.json"))->c_str(), std::ofstream::out | std::ofstream::trunc); | ||||
| 	file << modSettings.toString(); | ||||
| } | ||||
|  | ||||
| void ModsPresetState::createInitialPreset() | ||||
| { | ||||
| 	// TODO: scan mods directory for all its content? Probably unnecessary since this looks like new install, but who knows? | ||||
| 	modConfig["presets"]["default"]["mods"].Vector().push_back(JsonNode("vcmi")); | ||||
| } | ||||
|  | ||||
| void ModsPresetState::importInitialPreset() | ||||
| { | ||||
| 	JsonNode preset; | ||||
|  | ||||
| 	for(const auto & mod : modConfig["activeMods"].Struct()) | ||||
| 	{ | ||||
| 		if(mod.second["active"].Bool()) | ||||
| 			preset["mods"].Vector().push_back(JsonNode(mod.first)); | ||||
|  | ||||
| 		for(const auto & submod : mod.second["mods"].Struct()) | ||||
| 			preset["settings"][mod.first][submod.first] = submod.second["active"]; | ||||
| 	} | ||||
| 	modConfig["presets"]["default"] = preset; | ||||
| } | ||||
|  | ||||
| std::vector<TModID> ModsPresetState::getActiveMods() const | ||||
| { | ||||
| 	const std::string & currentPresetName = modConfig["activePreset"].String(); | ||||
| 	const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; | ||||
| 	const JsonNode & modsToActivateJson = currentPreset["mods"]; | ||||
| 	std::vector<TModID> modsToActivate = modsToActivateJson.convertTo<std::vector<TModID>>(); | ||||
|  | ||||
| 	modsToActivate.push_back(ModScope::scopeBuiltin()); | ||||
|  | ||||
| 	for(const auto & settings : currentPreset["settings"].Struct()) | ||||
| 	{ | ||||
| 		if(!vstd::contains(modsToActivate, settings.first)) | ||||
| 			continue; // settings for inactive mod | ||||
|  | ||||
| 		for(const auto & submod : settings.second.Struct()) | ||||
| 			if(submod.second.Bool()) | ||||
| 				modsToActivate.push_back(settings.first + '.' + submod.first); | ||||
| 	} | ||||
| 	return modsToActivate; | ||||
| } | ||||
|  | ||||
| ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad) | ||||
| { | ||||
| 	JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json")); | ||||
| 	coreModConfig.setModScope(ModScope::scopeBuiltin()); | ||||
| 	mods.try_emplace(ModScope::scopeBuiltin(), ModScope::scopeBuiltin(), coreModConfig); | ||||
|  | ||||
| 	for(auto modID : modsToLoad) | ||||
| 	{ | ||||
| 		if(ModScope::isScopeReserved(modID)) | ||||
| 		{ | ||||
| 			logMod->error("Can not load mod %s - this name is reserved for internal use!", modID); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		JsonNode modConfig(getModDescriptionFile(modID)); | ||||
| 		modConfig.setModScope(modID); | ||||
|  | ||||
| 		if(modConfig["modType"].isNull()) | ||||
| 		{ | ||||
| 			logMod->error("Can not load mod %s - invalid mod config file!", modID); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		mods.try_emplace(modID, modID, modConfig); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const ModDescription & ModsStorage::getMod(const TModID & fullID) const | ||||
| { | ||||
| 	return mods.at(fullID); | ||||
| } | ||||
|  | ||||
| ModManager::ModManager() | ||||
| 	: modsState(std::make_unique<ModsState>()) | ||||
| 	, modsPreset(std::make_unique<ModsPresetState>()) | ||||
| { | ||||
| 	std::vector<TModID> desiredModList = modsPreset->getActiveMods(); | ||||
| 	const std::vector<TModID> & installedModList = modsState->getAllMods(); | ||||
|  | ||||
| 	vstd::erase_if(desiredModList, [&](const TModID & mod){ | ||||
| 		return !vstd::contains(installedModList, mod); | ||||
| 	}); | ||||
|  | ||||
| 	modsStorage = std::make_unique<ModsStorage>(desiredModList); | ||||
|  | ||||
| 	generateLoadOrder(desiredModList); | ||||
| } | ||||
|  | ||||
| ModManager::~ModManager() = default; | ||||
|  | ||||
| const ModDescription & ModManager::getModDescription(const TModID & modID) const | ||||
| { | ||||
| 	return modsStorage->getMod(modID); | ||||
| } | ||||
|  | ||||
| bool ModManager::isModActive(const TModID & modID) const | ||||
| { | ||||
| 	return vstd::contains(activeMods, modID); | ||||
| } | ||||
|  | ||||
| const TModList & ModManager::getActiveMods() const | ||||
| { | ||||
| 	return activeMods; | ||||
| } | ||||
|  | ||||
| void ModManager::generateLoadOrder(std::vector<TModID> modsToResolve) | ||||
| { | ||||
| 	// Topological sort algorithm. | ||||
| 	boost::range::sort(modsToResolve); // Sort mods per name | ||||
| 	std::vector<TModID> sortedValidMods; // Vector keeps order of elements (LIFO) | ||||
| 	sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation | ||||
| 	std::set<TModID> resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements | ||||
| 	std::set<TModID> notResolvedModIDs(modsToResolve.begin(), modsToResolve.end()); // Use a set for validation for performance reason | ||||
|  | ||||
| 	// Mod is resolved if it has no dependencies or all its dependencies are already resolved | ||||
| 	auto isResolved = [&](const ModDescription & mod) -> bool | ||||
| 	{ | ||||
| 		if(mod.getDependencies().size() > resolvedModIDs.size()) | ||||
| 			return false; | ||||
|  | ||||
| 		for(const TModID & dependency : mod.getDependencies()) | ||||
| 			if(!vstd::contains(resolvedModIDs, dependency)) | ||||
| 				return false; | ||||
|  | ||||
| 		for(const TModID & softDependency : mod.getSoftDependencies()) | ||||
| 			if(vstd::contains(notResolvedModIDs, softDependency)) | ||||
| 				return false; | ||||
|  | ||||
| 		for(const TModID & conflict : mod.getConflicts()) | ||||
| 			if(vstd::contains(resolvedModIDs, conflict)) | ||||
| 				return false; | ||||
|  | ||||
| 		for(const TModID & reverseConflict : resolvedModIDs) | ||||
| 			if(vstd::contains(modsStorage->getMod(reverseConflict).getConflicts(), mod.getID())) | ||||
| 				return false; | ||||
|  | ||||
| 		return true; | ||||
| 	}; | ||||
|  | ||||
| 	while(true) | ||||
| 	{ | ||||
| 		std::set<TModID> resolvedOnCurrentTreeLevel; | ||||
| 		for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree | ||||
| 		{ | ||||
| 			if(isResolved(modsStorage->getMod(*it))) | ||||
| 			{ | ||||
| 				resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node children will be resolved on the next iteration | ||||
| 				sortedValidMods.push_back(*it); | ||||
| 				it = modsToResolve.erase(it); | ||||
| 				continue; | ||||
| 			} | ||||
| 			it++; | ||||
| 		} | ||||
| 		if(!resolvedOnCurrentTreeLevel.empty()) | ||||
| 		{ | ||||
| 			resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end()); | ||||
| 			for(const auto & it : resolvedOnCurrentTreeLevel) | ||||
| 				notResolvedModIDs.erase(it); | ||||
| 			continue; | ||||
| 		} | ||||
| 		// If there are no valid mods on the current mods tree level, no more mod can be resolved, should be ended. | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| 	activeMods = sortedValidMods; | ||||
| 	brokenMods = modsToResolve; | ||||
| } | ||||
|  | ||||
| //	modLoadErrors = std::make_unique<MetaString>(); | ||||
| // | ||||
| //	auto addErrorMessage = [this](const std::string & textID, const std::string & brokenModID, const std::string & missingModID) | ||||
| //	{ | ||||
| //		modLoadErrors->appendTextID(textID); | ||||
| // | ||||
| //		if (allMods.count(brokenModID)) | ||||
| //			modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); | ||||
| //		else | ||||
| //			modLoadErrors->replaceRawString(brokenModID); | ||||
| // | ||||
| //		if (allMods.count(missingModID)) | ||||
| //			modLoadErrors->replaceRawString(allMods.at(missingModID).getVerificationInfo().name); | ||||
| //		else | ||||
| //			modLoadErrors->replaceRawString(missingModID); | ||||
| // | ||||
| //	}; | ||||
| // | ||||
| //	// Left mods have unresolved dependencies, output all to log. | ||||
| //	for(const auto & brokenModID : modsToResolve) | ||||
| //	{ | ||||
| //		const CModInfo & brokenMod = allMods.at(brokenModID); | ||||
| //		bool showErrorMessage = false; | ||||
| //		for(const TModID & dependency : brokenMod.dependencies) | ||||
| //		{ | ||||
| //			if(!vstd::contains(resolvedModIDs, dependency) && brokenMod.config["modType"].String() != "Compatibility") | ||||
| //			{ | ||||
| //				addErrorMessage("vcmi.server.errors.modNoDependency", brokenModID, dependency); | ||||
| //				showErrorMessage = true; | ||||
| //			} | ||||
| //		} | ||||
| //		for(const TModID & conflict : brokenMod.conflicts) | ||||
| //		{ | ||||
| //			if(vstd::contains(resolvedModIDs, conflict)) | ||||
| //			{ | ||||
| //				addErrorMessage("vcmi.server.errors.modConflict", brokenModID, conflict); | ||||
| //				showErrorMessage = true; | ||||
| //			} | ||||
| //		} | ||||
| //		for(const TModID & reverseConflict : resolvedModIDs) | ||||
| //		{ | ||||
| //			if (vstd::contains(allMods.at(reverseConflict).conflicts, brokenModID)) | ||||
| //			{ | ||||
| //				addErrorMessage("vcmi.server.errors.modConflict", brokenModID, reverseConflict); | ||||
| //				showErrorMessage = true; | ||||
| //			} | ||||
| //		} | ||||
| // | ||||
| //		// some mods may in a (soft) dependency loop. | ||||
| //		if(!showErrorMessage && brokenMod.config["modType"].String() != "Compatibility") | ||||
| //		{ | ||||
| //			modLoadErrors->appendTextID("vcmi.server.errors.modDependencyLoop"); | ||||
| //			if (allMods.count(brokenModID)) | ||||
| //				modLoadErrors->replaceRawString(allMods.at(brokenModID).getVerificationInfo().name); | ||||
| //			else | ||||
| //				modLoadErrors->replaceRawString(brokenModID); | ||||
| //		} | ||||
| // | ||||
| //	} | ||||
| //	return sortedValidMods; | ||||
| //} | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
							
								
								
									
										95
									
								
								lib/modding/ModManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								lib/modding/ModManager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| /* | ||||
|  * ModManager.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../json/JsonNode.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| class JsonNode; | ||||
| class ModDescription; | ||||
| struct CModVersion; | ||||
|  | ||||
| using TModID = std::string; | ||||
| using TModList = std::vector<TModID>; | ||||
| using TModSet = std::set<TModID>; | ||||
|  | ||||
| /// Provides interface to access list of locally installed mods | ||||
| class ModsState : boost::noncopyable | ||||
| { | ||||
| 	TModList modList; | ||||
|  | ||||
| 	TModList scanModsDirectory(const std::string & modDir) const; | ||||
|  | ||||
| public: | ||||
| 	ModsState(); | ||||
|  | ||||
| 	TModList getAllMods() const; | ||||
| }; | ||||
|  | ||||
| /// Provides interface to access or change current mod preset | ||||
| class ModsPresetState : boost::noncopyable | ||||
| { | ||||
| 	JsonNode modConfig; | ||||
|  | ||||
| 	void saveConfiguration(const JsonNode & config); | ||||
|  | ||||
| 	void createInitialPreset(); | ||||
| 	void importInitialPreset(); | ||||
|  | ||||
| public: | ||||
| 	ModsPresetState(); | ||||
|  | ||||
| 	/// Returns true if mod is active in current preset | ||||
| 	bool isModActive(const TModID & modName) const; | ||||
|  | ||||
| 	void activateModInPreset(const TModID & modName); | ||||
| 	void dectivateModInAllPresets(const TModID & modName); | ||||
|  | ||||
| 	/// Returns list of all mods active in current preset. Mod order is unspecified | ||||
| 	TModList getActiveMods() const; | ||||
| }; | ||||
|  | ||||
| /// Provides access to mod properties | ||||
| class ModsStorage : boost::noncopyable | ||||
| { | ||||
| 	std::map<TModID, ModDescription> mods; | ||||
|  | ||||
| public: | ||||
| 	ModsStorage(const TModList & modsToLoad); | ||||
|  | ||||
| 	const ModDescription & getMod(const TModID & fullID) const; | ||||
| }; | ||||
|  | ||||
| /// Provides public interface to access mod state | ||||
| class ModManager : boost::noncopyable | ||||
| { | ||||
| 	/// all currently active mods, in their load order | ||||
| 	TModList activeMods; | ||||
|  | ||||
| 	/// Mods from current preset that failed to load due to invalid dependencies | ||||
| 	TModList brokenMods; | ||||
|  | ||||
| 	std::unique_ptr<ModsState> modsState; | ||||
| 	std::unique_ptr<ModsPresetState> modsPreset; | ||||
| 	std::unique_ptr<ModsStorage> modsStorage; | ||||
|  | ||||
| 	void generateLoadOrder(TModList desiredModList); | ||||
|  | ||||
| public: | ||||
| 	ModManager(); | ||||
| 	~ModManager(); | ||||
|  | ||||
| 	const ModDescription & getModDescription(const TModID & modID) const; | ||||
| 	const TModList & getActiveMods() const; | ||||
| 	bool isModActive(const TModID & modID) const; | ||||
| }; | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
| @@ -10,8 +10,8 @@ | ||||
| #include "StdInc.h" | ||||
| #include "ModVerificationInfo.h" | ||||
|  | ||||
| #include "CModInfo.h" | ||||
| #include "CModHandler.h" | ||||
| #include "ModDescription.h" | ||||
| #include "ModIncompatibility.h" | ||||
|  | ||||
| #include "../json/JsonNode.h" | ||||
| @@ -68,7 +68,7 @@ ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const | ||||
| 		if(modList.count(m)) | ||||
| 			continue; | ||||
|  | ||||
| 		if(VLC->modh->getModInfo(m).checkModGameplayAffecting()) | ||||
| 		if(VLC->modh->getModInfo(m).affectsGameplay()) | ||||
| 			result[m] = ModVerificationStatus::EXCESSIVE; | ||||
| 	} | ||||
|  | ||||
| @@ -88,8 +88,8 @@ ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		auto & localModInfo = VLC->modh->getModInfo(remoteModId).getVerificationInfo(); | ||||
| 		modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).checkModGameplayAffecting(); | ||||
| 		const auto & localVersion = VLC->modh->getModInfo(remoteModId).getVersion(); | ||||
| 		modAffectsGameplay |= VLC->modh->getModInfo(remoteModId).affectsGameplay(); | ||||
|  | ||||
| 		// skip it. Such mods should only be present in old saves or if mod changed and no longer affects gameplay | ||||
| 		if (!modAffectsGameplay) | ||||
| @@ -101,7 +101,7 @@ ModListVerificationStatus ModVerificationInfo::verifyListAgainstLocalMods(const | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		if(remoteModInfo.version != localModInfo.version) | ||||
| 		if(remoteModInfo.version != localVersion) | ||||
| 		{ | ||||
| 			result[remoteModId] = ModVerificationStatus::VERSION_MISMATCH; | ||||
| 			continue; | ||||
|   | ||||
| @@ -23,7 +23,7 @@ | ||||
| #include "../lib/mapping/CMapEditManager.h" | ||||
| #include "../lib/mapping/ObstacleProxy.h" | ||||
| #include "../lib/modding/CModHandler.h" | ||||
| #include "../lib/modding/CModInfo.h" | ||||
| #include "../lib/modding/ModDescription.h" | ||||
| #include "../lib/TerrainHandler.h" | ||||
| #include "../lib/CSkillHandler.h" | ||||
| #include "../lib/spells/CSpellHandler.h" | ||||
|   | ||||
| @@ -13,9 +13,8 @@ | ||||
| #include "maphandler.h" | ||||
| #include "mapview.h" | ||||
|  | ||||
| #include "../lib/modding/CModInfo.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
| struct ModVerificationInfo; | ||||
| using ModCompatibilityInfo = std::map<std::string, ModVerificationInfo>; | ||||
| class EditorObstaclePlacer; | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -11,9 +11,9 @@ | ||||
| #include "modsettings.h" | ||||
| #include "ui_modsettings.h" | ||||
| #include "../mapcontroller.h" | ||||
| #include "../../lib/modding/ModDescription.h" | ||||
| #include "../../lib/modding/CModHandler.h" | ||||
| #include "../../lib/mapping/CMapService.h" | ||||
| #include "../../lib/modding/CModInfo.h" | ||||
|  | ||||
| void traverseNode(QTreeWidgetItem * item, std::function<void(QTreeWidgetItem*)> action) | ||||
| { | ||||
| @@ -45,12 +45,12 @@ void ModSettings::initialize(MapController & c) | ||||
| 	QSet<QString> modsToProcess; | ||||
| 	ui->treeMods->blockSignals(true); | ||||
|  | ||||
| 	auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const CModInfo & modInfo) | ||||
| 	auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const ModDescription & modInfo) | ||||
| 	{ | ||||
| 		auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.getVerificationInfo().name), QString::fromStdString(modInfo.getVerificationInfo().version.toString())}); | ||||
| 		item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.identifier))); | ||||
| 		auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.getName()), QString::fromStdString(modInfo.getVersion().toString())}); | ||||
| 		item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.getID()))); | ||||
| 		item->setFlags(item->flags() | Qt::ItemIsUserCheckable); | ||||
| 		item->setCheckState(0, controller->map()->mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked); | ||||
| 		item->setCheckState(0, controller->map()->mods.count(modInfo.getID()) ? Qt::Checked : Qt::Unchecked); | ||||
| 		//set parent check | ||||
| 		if(parent && item->checkState(0) == Qt::Checked) | ||||
| 			parent->setCheckState(0, Qt::Checked); | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
| #include "../lib/mapping/CMap.h" | ||||
| #include "../lib/mapObjects/MapObjects.h" | ||||
| #include "../lib/modding/CModHandler.h" | ||||
| #include "../lib/modding/CModInfo.h" | ||||
| #include "../lib/modding/ModDescription.h" | ||||
| #include "../lib/spells/CSpellHandler.h" | ||||
|  | ||||
| Validator::Validator(const CMap * map, QWidget *parent) : | ||||
|   | ||||
| @@ -15,7 +15,8 @@ | ||||
| #include "../lib/json/JsonUtils.h" | ||||
| #include "../lib/VCMI_Lib.h" | ||||
| #include "../lib/modding/CModHandler.h" | ||||
| #include "../lib/modding/CModInfo.h" | ||||
| #include "../lib/modding/ModDescription.h" | ||||
| #include "../lib/modding/ModVerificationInfo.h" | ||||
|  | ||||
| GlobalLobbyProcessor::GlobalLobbyProcessor(CVCMIServer & owner) | ||||
| 	: owner(owner) | ||||
| @@ -161,7 +162,7 @@ JsonNode GlobalLobbyProcessor::getHostModList() const | ||||
|  | ||||
| 	for (auto const & modName : VLC->modh->getActiveMods()) | ||||
| 	{ | ||||
| 		if(VLC->modh->getModInfo(modName).checkModGameplayAffecting()) | ||||
| 		if(VLC->modh->getModInfo(modName).affectsGameplay()) | ||||
| 			info[modName] = VLC->modh->getModInfo(modName).getVerificationInfo(); | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user