1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-25 22:42:04 +02:00

Initial support for mod presets system

This commit is contained in:
Ivan Savenko
2024-10-16 12:52:08 +00:00
parent 7fdddee503
commit c57120f0dd
12 changed files with 221 additions and 91 deletions

View File

@@ -13,6 +13,7 @@ set(launcher_SRCS
modManager/cmodmanager.cpp modManager/cmodmanager.cpp
modManager/imageviewer_moc.cpp modManager/imageviewer_moc.cpp
modManager/chroniclesextractor.cpp modManager/chroniclesextractor.cpp
modManager/modsettingsstorage.cpp
settingsView/csettingsview_moc.cpp settingsView/csettingsview_moc.cpp
firstLaunch/firstlaunch_moc.cpp firstLaunch/firstlaunch_moc.cpp
main.cpp main.cpp
@@ -43,6 +44,7 @@ set(launcher_HEADERS
modManager/cmodmanager.h modManager/cmodmanager.h
modManager/imageviewer_moc.h modManager/imageviewer_moc.h
modManager/chroniclesextractor.h modManager/chroniclesextractor.h
modManager/modsettingsstorage.h
settingsView/csettingsview_moc.h settingsView/csettingsview_moc.h
firstLaunch/firstlaunch_moc.h firstLaunch/firstlaunch_moc.h
mainwindow_moc.h mainwindow_moc.h

View File

@@ -33,8 +33,8 @@ QString CModEntry::sizeToString(double size)
return QCoreApplication::translate("File size", sizes[index]).arg(QString::number(size, 'f', 1)); return QCoreApplication::translate("File size", sizes[index]).arg(QString::number(size, 'f', 1));
} }
CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, QVariantMap modSettings, QString modname) CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname)
: repository(repository), localData(localData), modSettings(modSettings), modname(modname) : repository(repository), localData(localData), modActive(modActive), modname(modname)
{ {
} }
@@ -46,7 +46,7 @@ bool CModEntry::isEnabled() const
if (!isVisible()) if (!isVisible())
return false; return false;
return modSettings["active"].toBool(); return modActive;
} }
bool CModEntry::isDisabled() const bool CModEntry::isDisabled() const
@@ -250,9 +250,9 @@ void CModList::setLocalModList(QVariantMap data)
cachedMods.clear(); cachedMods.clear();
} }
void CModList::setModSettings(QVariant data) void CModList::setModSettings(std::shared_ptr<ModSettingsStorage> data)
{ {
modSettings = data.toMap(); modSettings = data;
cachedMods.clear(); cachedMods.clear();
} }
@@ -294,46 +294,23 @@ CModEntry CModList::getModUncached(QString modname) const
{ {
QVariantMap repo; QVariantMap repo;
QVariantMap local = localModList[modname].toMap(); QVariantMap local = localModList[modname].toMap();
QVariantMap settings;
QString path = modname; QString path = modname;
path = "/" + path.replace(".", "/mods/"); path = "/" + path.replace(".", "/mods/");
QVariant conf = getValue(modSettings, path);
if(conf.isNull()) bool modActive = modSettings->isModActive(modname);
if(modActive)
{ {
settings["active"] = !local.value("keepDisabled").toBool(); QString rootModName = modname.section('.', 0, 1);
} if (!modSettings->isModActive(rootModName))
else modActive = false; // parent mod is inactive -> submod is also inactive
{
if(!conf.toMap().isEmpty())
{
settings = conf.toMap();
if(settings.value("active").isNull())
settings["active"] = !local.value("keepDisabled").toBool();
}
else
settings.insert("active", conf);
} }
if(settings["active"].toBool()) if(modActive)
{
QString rootPath = path.section('/', 0, 1);
if(path != rootPath)
{
conf = getValue(modSettings, rootPath);
const auto confMap = conf.toMap();
if(!conf.isNull() && !confMap["active"].isNull() && !confMap["active"].toBool())
{
settings = confMap;
}
}
}
if(settings.value("active").toBool())
{ {
if(!::isCompatible(local.value("compatibility").toMap())) if(!::isCompatible(local.value("compatibility").toMap()))
settings["active"] = false; modActive = false; // mod not compatible with our version of vcmi -> inactive
} }
for(auto entry : repositories) for(auto entry : repositories)
@@ -366,7 +343,7 @@ CModEntry CModList::getModUncached(QString modname) const
} }
} }
return CModEntry(repo, local, settings, modname); return CModEntry(repo, local, modActive, modname);
} }
bool CModList::hasMod(QString modname) const bool CModList::hasMod(QString modname) const

View File

@@ -13,6 +13,8 @@
#include <QVariant> #include <QVariant>
#include <QVector> #include <QVector>
#include "modsettingsstorage.h"
namespace ModStatus namespace ModStatus
{ {
enum EModStatus enum EModStatus
@@ -30,13 +32,13 @@ class CModEntry
// repository contains newest version only (if multiple are available) // repository contains newest version only (if multiple are available)
QVariantMap repository; QVariantMap repository;
QVariantMap localData; QVariantMap localData;
QVariantMap modSettings; bool modActive;
QString modname; QString modname;
QVariant getValueImpl(QString value, bool localized) const; QVariant getValueImpl(QString value, bool localized) const;
public: public:
CModEntry(QVariantMap repository, QVariantMap localData, QVariantMap modSettings, QString modname); CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname);
// installed and enabled // installed and enabled
bool isEnabled() const; bool isEnabled() const;
@@ -80,7 +82,7 @@ class CModList
{ {
QVector<QVariantMap> repositories; QVector<QVariantMap> repositories;
QVariantMap localModList; QVariantMap localModList;
QVariantMap modSettings; std::shared_ptr<ModSettingsStorage> modSettings;
mutable QMap<QString, CModEntry> cachedMods; mutable QMap<QString, CModEntry> cachedMods;
@@ -92,7 +94,7 @@ public:
virtual void reloadRepositories(); virtual void reloadRepositories();
virtual void addRepository(QVariantMap data); virtual void addRepository(QVariantMap data);
virtual void setLocalModList(QVariantMap data); virtual void setLocalModList(QVariantMap data);
virtual void setModSettings(QVariant data); virtual void setModSettings(std::shared_ptr<ModSettingsStorage> data);
virtual void modChanged(QString modID); virtual void modChanged(QString modID);
// returns mod by name. Note: mod MUST exist // returns mod by name. Note: mod MUST exist

View File

@@ -192,6 +192,8 @@ void CModListModel::resetRepositories()
void CModListModel::modChanged(QString modID) void CModListModel::modChanged(QString modID)
{ {
CModList::modChanged(modID);
int index = modNameToID.indexOf(modID); int index = modNameToID.indexOf(modID);
QModelIndex parent = this->parent(createIndex(0, 0, index)); QModelIndex parent = this->parent(createIndex(0, 0, index));
int row = modIndex[modIndexToName(parent)].indexOf(modID); int row = modIndex[modIndexToName(parent)].indexOf(modID);

View File

@@ -69,15 +69,10 @@ CModManager::CModManager(CModList * modList)
loadModSettings(); loadModSettings();
} }
QString CModManager::settingsPath()
{
return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json");
}
void CModManager::loadModSettings() void CModManager::loadModSettings()
{ {
modSettings = JsonUtils::JsonFromFile(settingsPath()).toMap(); modSettings = std::make_shared<ModSettingsStorage>();
modList->setModSettings(modSettings["activeMods"]); modList->setModSettings(modSettings);
} }
void CModManager::resetRepositories() void CModManager::resetRepositories()
@@ -248,35 +243,11 @@ bool CModManager::canDisableMod(QString modname)
return true; return true;
} }
static QVariant writeValue(QString path, QVariantMap input, QVariant value)
{
if(path.size() > 1)
{
QString entryName = path.section('/', 0, 1);
QString remainder = "/" + path.section('/', 2, -1);
entryName.remove(0, 1);
input.insert(entryName, writeValue(remainder, input.value(entryName).toMap(), value));
return input;
}
else
{
return value;
}
}
bool CModManager::doEnableMod(QString mod, bool on) bool CModManager::doEnableMod(QString mod, bool on)
{ {
QString path = mod; modSettings->setModActive(mod, on);
path = "/activeMods/" + path.replace(".", "/mods/") + "/active";
modSettings = writeValue(path, modSettings, QVariant(on)).toMap();
modList->setModSettings(modSettings["activeMods"]);
modList->modChanged(mod); modList->modChanged(mod);
JsonUtils::JsonToFile(settingsPath(), modSettings);
return true; return true;
} }

View File

@@ -10,6 +10,7 @@
#pragma once #pragma once
#include "cmodlist.h" #include "cmodlist.h"
#include "modsettingsstorage.h"
class CModManager : public QObject class CModManager : public QObject
{ {
@@ -17,14 +18,12 @@ class CModManager : public QObject
CModList * modList; CModList * modList;
QString settingsPath();
// check-free version of public method // check-free version of public method
bool doEnableMod(QString mod, bool on); bool doEnableMod(QString mod, bool on);
bool doInstallMod(QString mod, QString archivePath); bool doInstallMod(QString mod, QString archivePath);
bool doUninstallMod(QString mod); bool doUninstallMod(QString mod);
QVariantMap modSettings; std::shared_ptr<ModSettingsStorage> modSettings;
QVariantMap localMods; QVariantMap localMods;
QStringList recentErrors; QStringList recentErrors;

View File

@@ -0,0 +1,123 @@
/*
* modsettignsstorage.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 "modsettingsstorage.h"
#include "../../lib/VCMIDirs.h"
#include "../vcmiqt/jsonutils.h"
static QVariant writeValue(QString path, QVariantMap input, QVariant value)
{
if(path.size() > 1)
{
QString entryName = path.section('/', 0, 1);
QString remainder = "/" + path.section('/', 2, -1);
entryName.remove(0, 1);
input.insert(entryName, writeValue(remainder, input.value(entryName).toMap(), value));
return input;
}
else
{
return value;
}
}
ModSettingsStorage::ModSettingsStorage()
{
config = JsonUtils::JsonFromFile(settingsPath()).toMap();
// TODO: import from 1.5 format
}
QString ModSettingsStorage::settingsPath() const
{
return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json");
}
void ModSettingsStorage::setRootModActive(const QString & modName, bool on)
{
QString presetName = getActivePreset();
QStringList activeMods = getActiveMods();
assert(modName.count('.') == 0); // this method should never be used for submods
if (on)
activeMods.push_back(modName);
else
activeMods.removeAll(modName);
config = writeValue("/presets/" + presetName + "/mods", config, activeMods).toMap();
JsonUtils::JsonToFile(settingsPath(), config);
}
void ModSettingsStorage::setModSettingActive(const QString & modName, bool on)
{
QString presetName = getActivePreset();
QString rootModName = modName.section('.', 0, 0);
QString settingName = modName.section('.', 1);
QVariantMap modSettings = getModSettings(rootModName);
assert(modName.count('.') != 0); // this method should only be used for submods
modSettings.insert(settingName, QVariant(on));
config = writeValue("/presets/" + presetName + "/settings/" + rootModName, config, modSettings).toMap();
JsonUtils::JsonToFile(settingsPath(), config);
}
void ModSettingsStorage::setModActive(const QString & modName, bool on)
{
if (modName.contains('.'))
setModSettingActive(modName, on);
else
setRootModActive(modName, on);
}
void ModSettingsStorage::setActivePreset(const QString & presetName)
{
config.insert("activePreset", QVariant(presetName));
}
QString ModSettingsStorage::getActivePreset() const
{
return config["activePreset"].toString();
}
bool ModSettingsStorage::isModActive(const QString & modName) const
{
if (modName.contains('.'))
{
QString rootModName = modName.section('.', 0, 0);
QString settingName = modName.section('.', 1);
return getModSettings(rootModName)[settingName].toBool();
}
else
return getActiveMods().contains(modName);
}
QStringList ModSettingsStorage::getActiveMods() const
{
return getActivePresetData()["mods"].toStringList();
}
QVariantMap ModSettingsStorage::getActivePresetData() const
{
QString presetName = getActivePreset();
return config["presets"].toMap()[presetName].toMap();
}
QVariantMap ModSettingsStorage::getModSettings(const QString & modName) const
{
QString presetName = getActivePreset();
return getActivePresetData()["settings"].toMap()[modName].toMap();
}

View File

@@ -0,0 +1,37 @@
/*
* modsettignsstorage.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 <QVariantMap>
class ModSettingsStorage
{
QVariantMap config;
QString settingsPath() const;
void setRootModActive(const QString & modName, bool on);
void setModSettingActive(const QString & modName, bool on);
QVariantMap getActivePresetData() const;
QStringList getActiveMods() const;
QVariantMap getModSettings(const QString & modName) const;
public:
ModSettingsStorage();
void setModActive(const QString & modName, bool on);
void setActivePreset(const QString & presetName);
QString getActivePreset() const;
bool isModActive(const QString & modName) const;
//QStringList getPresetsList() const;
};

View File

@@ -238,13 +238,13 @@ std::vector<std::string> CModHandler::getModList(const std::string & path) const
void CModHandler::loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, bool enableMods) 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)) for(const std::string & modName : getModList(path))
loadOneMod(modName, parent, modSettings, enableMods); loadOneMod(modName, parent, modSettings, modsToActivate, enableMods);
} }
void CModHandler::loadOneMod(std::string modName, const std::string & parent, const JsonNode & modSettings, bool 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); boost::to_lower(modName);
std::string modFullName = parent.empty() ? modName : parent + '.' + modName; std::string modFullName = parent.empty() ? modName : parent + '.' + modName;
@@ -257,7 +257,8 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co
if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName))) if(CResourceHandler::get("initial")->existsResource(CModInfo::getModFile(modFullName)))
{ {
CModInfo mod(modFullName, modSettings[modName], JsonNode(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 if (!parent.empty()) // this is submod, add parent to dependencies
mod.dependencies.insert(parent); mod.dependencies.insert(parent);
@@ -265,7 +266,7 @@ void CModHandler::loadOneMod(std::string modName, const std::string & parent, co
if (mod.isEnabled() && enableMods) if (mod.isEnabled() && enableMods)
activeMods.push_back(modFullName); activeMods.push_back(modFullName);
loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.isEnabled()); loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], modsToActivate, enableMods && mod.isEnabled());
} }
} }
@@ -274,9 +275,27 @@ void CModHandler::loadMods()
JsonNode modConfig; JsonNode modConfig;
modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json")); modConfig = loadModSettings(JsonPath::builtin("config/modSettings.json"));
loadMods("", "", modConfig["activeMods"], true); 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>>();
coreMod = std::make_unique<CModInfo>(ModScope::scopeBuiltin(), modConfig[ModScope::scopeBuiltin()], JsonNode(JsonPath::builtin("config/gameConfig.json"))); 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> CModHandler::getAllMods() const

View File

@@ -42,11 +42,11 @@ class DLL_LINKAGE CModHandler final : boost::noncopyable
* @param modsToResolve list of valid mod IDs (checkDependencies returned true - TODO: Clarify it.) * @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 * @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<TModID> validateAndSortDependencies(std::vector <TModID> modsToResolve) const;
std::vector<std::string> getModList(const std::string & path) const; std::vector<std::string> getModList(const std::string & path) const;
void loadMods(const std::string & path, const std::string & parent, const JsonNode & modSettings, bool enableMods); 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, 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); void loadTranslation(const TModID & modName);
CModVersion getModVersion(TModID modName) const; CModVersion getModVersion(TModID modName) const;

View File

@@ -40,12 +40,12 @@ CModInfo::CModInfo():
} }
CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config): CModInfo::CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive):
identifier(identifier), identifier(identifier),
dependencies(readModList(config["depends"])), dependencies(readModList(config["depends"])),
softDependencies(readModList(config["softDepends"])), softDependencies(readModList(config["softDepends"])),
conflicts(readModList(config["conflicts"])), conflicts(readModList(config["conflicts"])),
explicitlyEnabled(false), explicitlyEnabled(isActive),
implicitlyEnabled(true), implicitlyEnabled(true),
validation(PENDING), validation(PENDING),
config(addMeta(config, identifier)) config(addMeta(config, identifier))
@@ -110,11 +110,9 @@ void CModInfo::loadLocalData(const JsonNode & data)
{ {
bool validated = false; bool validated = false;
implicitlyEnabled = true; implicitlyEnabled = true;
explicitlyEnabled = !config["keepDisabled"].Bool();
verificationInfo.checksum = 0; verificationInfo.checksum = 0;
if (data.isStruct()) if (data.isStruct())
{ {
explicitlyEnabled = data["active"].Bool();
validated = data["validated"].Bool(); validated = data["validated"].Bool();
updateChecksum(strtol(data["checksum"].String().c_str(), nullptr, 16)); updateChecksum(strtol(data["checksum"].String().c_str(), nullptr, 16));
} }

View File

@@ -55,7 +55,7 @@ public:
JsonNode config; JsonNode config;
CModInfo(); CModInfo();
CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config); CModInfo(const std::string & identifier, const JsonNode & local, const JsonNode & config, bool isActive);
JsonNode saveLocalData() const; JsonNode saveLocalData() const;
void updateChecksum(ui32 newChecksum); void updateChecksum(ui32 newChecksum);