diff --git a/client/globalLobby/GlobalLobbyRoomWindow.cpp b/client/globalLobby/GlobalLobbyRoomWindow.cpp index f744dca2d..943737b72 100644 --- a/client/globalLobby/GlobalLobbyRoomWindow.cpp +++ b/client/globalLobby/GlobalLobbyRoomWindow.cpp @@ -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); } diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 69cdb576b..6b639d733 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -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 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); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index aca9b5919..85e1c95a8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -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 diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 6c7bf82c4..7cfef68cf 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -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" diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 91d3da34b..b5146f2e4 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -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(); identifiersHandler = std::make_unique(); - modh->loadMods(); logGlobal->info("\tMod handler: %d ms", loadTime.getDiff()); modh->loadModFilesystems(); diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index e3df56e34..4840b3f87 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -212,6 +212,7 @@ ISimpleResourceLoader * CResourceHandler::get() ISimpleResourceLoader * CResourceHandler::get(const std::string & identifier) { + assert(knownLoaders.count(identifier)); return knownLoaders.at(identifier); } diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 830245d12..61a1958b0 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -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; diff --git a/lib/modding/ActiveModsInSaveList.cpp b/lib/modding/ActiveModsInSaveList.cpp index 17e8927f0..c4620c170 100644 --- a/lib/modding/ActiveModsInSaveList.cpp +++ b/lib/modding/ActiveModsInSaveList.cpp @@ -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 ActiveModsInSaveList::getActiveGameplayAffectingMods() { std::vector 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::mapmodh->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()) diff --git a/lib/modding/ActiveModsInSaveList.h b/lib/modding/ActiveModsInSaveList.h index 0d51ed4f3..d89244788 100644 --- a/lib/modding/ActiveModsInSaveList.h +++ b/lib/modding/ActiveModsInSaveList.h @@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN class ActiveModsInSaveList { std::vector 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 & modList); @@ -29,7 +29,10 @@ public: std::vector activeMods = getActiveGameplayAffectingMods(); h & activeMods; for(const auto & m : activeMods) - h & getVerificationInfo(m); + { + ModVerificationInfo info = getVerificationInfo(m); + h & info; + } } else { diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index ed98a37bb..6ca90688a 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -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()) - , coreMod(std::make_unique()) + , modManager(std::make_unique()) { } CModHandler::~CModHandler() = default; -// currentList is passed by value to get current list of depending mods -bool CModHandler::hasCircularDependency(const TModID & modID, std::set 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 CModHandler::validateAndSortDependencies(std::vector modsToResolve) const -{ - // Topological sort algorithm. - // TODO: Investigate possible ways to improve performance. - boost::range::sort(modsToResolve); // Sort mods per name - std::vector sortedValidMods; // Vector keeps order of elements (LIFO) - sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation - std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements - std::set 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 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(); - - 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 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 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 & 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 & 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 modsToActivate = modsToActivateJson.convertTo>(); - - 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(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json")), true); -} - std::vector CModHandler::getAllMods() const { - std::vector 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 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(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(&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(&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(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(&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(&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 modFilesystems; - std::map 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 & 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 leftResources = leftFilesystem->getFilteredFiles(filter); + std::unordered_set 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 leftResources = modFilesystems[leftModName]->getFilteredFiles(filter); - std::unordered_set 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 CModHandler::getModDependencies(const TModID & modId) const @@ -489,11 +232,9 @@ std::set CModHandler::getModDependencies(const TModID & modId) const std::set 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 CModHandler::getModDependencies(const TModID & modId, bool & is std::set 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 CModHandler::getModEnabledSoftDependencies(const TModID & modId) const { std::set 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 diff --git a/lib/modding/CModHandler.h b/lib/modding/CModHandler.h index ccc4bc540..1ea49daef 100644 --- a/lib/modding/CModHandler.h +++ b/lib/modding/CModHandler.h @@ -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 allMods; - std::vector activeMods;//active mods, in order in which they were loaded - std::unique_ptr coreMod; - mutable std::unique_ptr modLoadErrors; + std::unique_ptr modManager; - bool hasCircularDependency(const TModID & mod, std::set currentList = std::set()) 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 validateAndSortDependencies(std::vector modsToResolve) const; - - std::vector getModList(const std::string & path) const; - void loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods); - void loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, const std::vector & modsToActivate, bool enableMods); void loadTranslation(const TModID & modName); - - CModVersion getModVersion(TModID modName) const; + void checkModFilesystemsConflicts(const std::map & modFilesystems); public: - std::shared_ptr content; //(!)Do not serialize FIXME: make private + std::shared_ptr 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(); diff --git a/lib/modding/CModInfo.cpp b/lib/modding/CModInfo.cpp deleted file mode 100644 index aa39b4e7d..000000000 --- a/lib/modding/CModInfo.cpp +++ /dev/null @@ -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 CModInfo::readModList(const JsonNode & input) -{ - std::set result; - - for (auto const & string : input.convertTo>()) - 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 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 diff --git a/lib/modding/CModInfo.h b/lib/modding/CModInfo.h deleted file mode 100644 index fd0f8319e..000000000 --- a/lib/modding/CModInfo.h +++ /dev/null @@ -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 modGameplayAffecting; - - static std::set 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 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 softDependencies; - - /// list of mods that can't be used in the same time as this one - std::set 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 diff --git a/lib/modding/ContentTypeHandler.cpp b/lib/modding/ContentTypeHandler.cpp index 2696080fe..04f21b4f1 100644 --- a/lib/modding/ContentTypeHandler.cpp +++ b/lib/modding/ContentTypeHandler.cpp @@ -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>] - 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>] +// 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; diff --git a/lib/modding/ContentTypeHandler.h b/lib/modding/ContentTypeHandler.h index a623cb226..746552389 100644 --- a/lib/modding/ContentTypeHandler.h +++ b/lib/modding/ContentTypeHandler.h @@ -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 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(); diff --git a/lib/modding/ModDescription.cpp b/lib/modding/ModDescription.cpp new file mode 100644 index 000000000..51dfb0f03 --- /dev/null +++ b/lib/modding/ModDescription.cpp @@ -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(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 diff --git a/lib/modding/ModDescription.h b/lib/modding/ModDescription.h new file mode 100644 index 000000000..ccb1b5947 --- /dev/null +++ b/lib/modding/ModDescription.h @@ -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; +using TModSet = std::set; + +class DLL_LINKAGE ModDescription : boost::noncopyable +{ + TModID identifier; + TModSet dependencies; + TModSet softDependencies; + TModSet conflicts; + + std::unique_ptr 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 diff --git a/lib/modding/ModManager.cpp b/lib/modding/ModManager.cpp new file mode 100644 index 000000000..011b3bae9 --- /dev/null +++ b/lib/modding/ModManager.cpp @@ -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 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 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 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 ModsPresetState::getActiveMods() const +{ + const std::string & currentPresetName = modConfig["activePreset"].String(); + const JsonNode & currentPreset = modConfig["presets"][currentPresetName]; + const JsonNode & modsToActivateJson = currentPreset["mods"]; + std::vector modsToActivate = modsToActivateJson.convertTo>(); + + 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 & 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()) + , modsPreset(std::make_unique()) +{ + std::vector desiredModList = modsPreset->getActiveMods(); + const std::vector & installedModList = modsState->getAllMods(); + + vstd::erase_if(desiredModList, [&](const TModID & mod){ + return !vstd::contains(installedModList, mod); + }); + + modsStorage = std::make_unique(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 modsToResolve) +{ + // Topological sort algorithm. + boost::range::sort(modsToResolve); // Sort mods per name + std::vector sortedValidMods; // Vector keeps order of elements (LIFO) + sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation + std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements + std::set 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 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(); +// +// 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 diff --git a/lib/modding/ModManager.h b/lib/modding/ModManager.h new file mode 100644 index 000000000..67888920d --- /dev/null +++ b/lib/modding/ModManager.h @@ -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; +using TModSet = std::set; + +/// 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 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; + std::unique_ptr modsPreset; + std::unique_ptr 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 diff --git a/lib/modding/ModVerificationInfo.cpp b/lib/modding/ModVerificationInfo.cpp index db2c7370a..ab187cd15 100644 --- a/lib/modding/ModVerificationInfo.cpp +++ b/lib/modding/ModVerificationInfo.cpp @@ -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; diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 7b50a9e09..bc10d558d 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -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" diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 7b8a246eb..55235f05f 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.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; class EditorObstaclePlacer; VCMI_LIB_NAMESPACE_END diff --git a/mapeditor/mapsettings/modsettings.cpp b/mapeditor/mapsettings/modsettings.cpp index 330cb9c82..931566fb7 100644 --- a/mapeditor/mapsettings/modsettings.cpp +++ b/mapeditor/mapsettings/modsettings.cpp @@ -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 action) { @@ -45,12 +45,12 @@ void ModSettings::initialize(MapController & c) QSet 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); diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index 17c1d2dcc..a2d965e49 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -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) : diff --git a/server/GlobalLobbyProcessor.cpp b/server/GlobalLobbyProcessor.cpp index 15fd376f7..418fad4da 100644 --- a/server/GlobalLobbyProcessor.cpp +++ b/server/GlobalLobbyProcessor.cpp @@ -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(); }