mirror of
https://github.com/vcmi/vcmi.git
synced 2024-11-21 17:17:06 +02:00
e3ed4eda44
Tested by enabling dragging on desktop. According to profiler, most of time was spent on generating mod information. Cheap to do once, but it looks like Qt does this A LOT. Added simple caching & reduced copying. Seems to be working fine on PC, but not yet confirmed whether same goes for mobile
435 lines
9.7 KiB
C++
435 lines
9.7 KiB
C++
/*
|
|
* cmodlist.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 "cmodlist.h"
|
|
|
|
#include "../lib/CConfigHandler.h"
|
|
#include "../../lib/filesystem/CFileInputStream.h"
|
|
#include "../../lib/GameConstants.h"
|
|
#include "../../lib/modding/CModVersion.h"
|
|
|
|
QString CModEntry::sizeToString(double size)
|
|
{
|
|
static const std::array sizes {
|
|
QT_TRANSLATE_NOOP("File size", "%1 B"),
|
|
QT_TRANSLATE_NOOP("File size", "%1 KiB"),
|
|
QT_TRANSLATE_NOOP("File size", "%1 MiB"),
|
|
QT_TRANSLATE_NOOP("File size", "%1 GiB"),
|
|
QT_TRANSLATE_NOOP("File size", "%1 TiB")
|
|
};
|
|
size_t index = 0;
|
|
while(size > 1024 && index < sizes.size())
|
|
{
|
|
size /= 1024;
|
|
index++;
|
|
}
|
|
return QCoreApplication::translate("File size", sizes[index]).arg(QString::number(size, 'f', 1));
|
|
}
|
|
|
|
CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, QVariantMap modSettings, QString modname)
|
|
: repository(repository), localData(localData), modSettings(modSettings), modname(modname)
|
|
{
|
|
}
|
|
|
|
bool CModEntry::isEnabled() const
|
|
{
|
|
if(!isInstalled())
|
|
return false;
|
|
|
|
if (!isVisible())
|
|
return false;
|
|
|
|
return modSettings["active"].toBool();
|
|
}
|
|
|
|
bool CModEntry::isDisabled() const
|
|
{
|
|
if(!isInstalled())
|
|
return false;
|
|
return !isEnabled();
|
|
}
|
|
|
|
bool CModEntry::isAvailable() const
|
|
{
|
|
if(isInstalled())
|
|
return false;
|
|
return !repository.isEmpty();
|
|
}
|
|
|
|
bool CModEntry::isUpdateable() const
|
|
{
|
|
if(!isInstalled())
|
|
return false;
|
|
|
|
auto installedVer = localData["installedVersion"].toString().toStdString();
|
|
auto availableVer = repository["latestVersion"].toString().toStdString();
|
|
|
|
return (CModVersion::fromString(installedVer) < CModVersion::fromString(availableVer));
|
|
}
|
|
|
|
bool isCompatible(const QVariantMap & compatibility)
|
|
{
|
|
auto compatibleMin = CModVersion::fromString(compatibility["min"].toString().toStdString());
|
|
auto compatibleMax = CModVersion::fromString(compatibility["max"].toString().toStdString());
|
|
|
|
return (compatibleMin.isNull() || CModVersion::GameVersion().compatible(compatibleMin, true, true))
|
|
&& (compatibleMax.isNull() || compatibleMax.compatible(CModVersion::GameVersion(), true, true));
|
|
}
|
|
|
|
bool CModEntry::isCompatible() const
|
|
{
|
|
return ::isCompatible(localData["compatibility"].toMap());
|
|
}
|
|
|
|
bool CModEntry::isEssential() const
|
|
{
|
|
return getName() == "vcmi";
|
|
}
|
|
|
|
bool CModEntry::isInstalled() const
|
|
{
|
|
return !localData.isEmpty();
|
|
}
|
|
|
|
bool CModEntry::isVisible() const
|
|
{
|
|
if (isCompatibilityPatch())
|
|
{
|
|
if (isSubmod())
|
|
return false;
|
|
}
|
|
|
|
if (isTranslation())
|
|
{
|
|
// Do not show not installed translation mods to languages other than player language
|
|
if (localData.empty() && getBaseValue("language") != QString::fromStdString(settings["general"]["language"].String()) )
|
|
return false;
|
|
}
|
|
|
|
return !localData.isEmpty() || (!repository.isEmpty() && !repository.contains("mod"));
|
|
}
|
|
|
|
bool CModEntry::isTranslation() const
|
|
{
|
|
return getBaseValue("modType").toString() == "Translation";
|
|
}
|
|
|
|
bool CModEntry::isCompatibilityPatch() const
|
|
{
|
|
return getBaseValue("modType").toString() == "Compatibility";
|
|
}
|
|
|
|
bool CModEntry::isSubmod() const
|
|
{
|
|
return getName().contains('.');
|
|
}
|
|
|
|
int CModEntry::getModStatus() const
|
|
{
|
|
int status = 0;
|
|
if(isEnabled())
|
|
status |= ModStatus::ENABLED;
|
|
if(isInstalled())
|
|
status |= ModStatus::INSTALLED;
|
|
if(isUpdateable())
|
|
status |= ModStatus::UPDATEABLE;
|
|
|
|
return status;
|
|
}
|
|
|
|
QString CModEntry::getName() const
|
|
{
|
|
return modname;
|
|
}
|
|
|
|
QVariant CModEntry::getValue(QString value) const
|
|
{
|
|
return getValueImpl(value, true);
|
|
}
|
|
|
|
QStringList CModEntry::getDependencies() const
|
|
{
|
|
QStringList result;
|
|
for (auto const & entry : getValue("depends").toStringList())
|
|
result.push_back(entry.toLower());
|
|
return result;
|
|
}
|
|
|
|
QStringList CModEntry::getConflicts() const
|
|
{
|
|
QStringList result;
|
|
for (auto const & entry : getValue("conflicts").toStringList())
|
|
result.push_back(entry.toLower());
|
|
return result;
|
|
}
|
|
|
|
QVariant CModEntry::getBaseValue(QString value) const
|
|
{
|
|
return getValueImpl(value, false);
|
|
}
|
|
|
|
QVariant CModEntry::getValueImpl(QString value, bool localized) const
|
|
|
|
{
|
|
QString langValue = QString::fromStdString(settings["general"]["language"].String());
|
|
|
|
// Priorities
|
|
// 1) data from newest version
|
|
// 2) data from preferred language
|
|
|
|
bool useRepositoryData = repository.contains(value);
|
|
|
|
if(repository.contains(value) && localData.contains(value))
|
|
{
|
|
// value is present in both repo and locally installed. Select one from latest version
|
|
auto installedVer = localData["installedVersion"].toString().toStdString();
|
|
auto availableVer = repository["latestVersion"].toString().toStdString();
|
|
|
|
useRepositoryData = CModVersion::fromString(installedVer) < CModVersion::fromString(availableVer);
|
|
}
|
|
|
|
auto & storage = useRepositoryData ? repository : localData;
|
|
|
|
if(localized && storage.contains(langValue))
|
|
{
|
|
auto langStorage = storage[langValue].toMap();
|
|
if (langStorage.contains(value))
|
|
return langStorage[value];
|
|
}
|
|
|
|
if(storage.contains(value))
|
|
return storage[value];
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariantMap CModList::copyField(QVariantMap data, QString from, QString to) const
|
|
{
|
|
QVariantMap renamed;
|
|
|
|
for(auto it = data.begin(); it != data.end(); it++)
|
|
{
|
|
QVariantMap modConf = it.value().toMap();
|
|
|
|
modConf.insert(to, modConf.value(from));
|
|
renamed.insert(it.key(), modConf);
|
|
}
|
|
return renamed;
|
|
}
|
|
|
|
void CModList::reloadRepositories()
|
|
{
|
|
cachedMods.clear();
|
|
}
|
|
|
|
void CModList::resetRepositories()
|
|
{
|
|
repositories.clear();
|
|
cachedMods.clear();
|
|
}
|
|
|
|
void CModList::addRepository(QVariantMap data)
|
|
{
|
|
for(auto & key : data.keys())
|
|
data[key.toLower()] = data.take(key);
|
|
repositories.push_back(copyField(data, "version", "latestVersion"));
|
|
|
|
cachedMods.clear();
|
|
}
|
|
|
|
void CModList::setLocalModList(QVariantMap data)
|
|
{
|
|
localModList = copyField(data, "version", "installedVersion");
|
|
cachedMods.clear();
|
|
}
|
|
|
|
void CModList::setModSettings(QVariant data)
|
|
{
|
|
modSettings = data.toMap();
|
|
cachedMods.clear();
|
|
}
|
|
|
|
void CModList::modChanged(QString modID)
|
|
{
|
|
cachedMods.clear();
|
|
}
|
|
|
|
static QVariant getValue(QVariant input, QString path)
|
|
{
|
|
if(path.size() > 1)
|
|
{
|
|
QString entryName = path.section('/', 0, 1);
|
|
QString remainder = "/" + path.section('/', 2, -1);
|
|
|
|
entryName.remove(0, 1);
|
|
return getValue(input.toMap().value(entryName), remainder);
|
|
}
|
|
else
|
|
{
|
|
return input;
|
|
}
|
|
}
|
|
|
|
const CModEntry & CModList::getMod(QString modName) const
|
|
{
|
|
modName = modName.toLower();
|
|
|
|
auto it = cachedMods.find(modName);
|
|
|
|
if (it != cachedMods.end())
|
|
return it.value();
|
|
|
|
auto itNew = cachedMods.insert(modName, getModUncached(modName));
|
|
return *itNew;
|
|
}
|
|
|
|
CModEntry CModList::getModUncached(QString modname) const
|
|
{
|
|
QVariantMap repo;
|
|
QVariantMap local = localModList[modname].toMap();
|
|
QVariantMap settings;
|
|
|
|
QString path = modname;
|
|
path = "/" + path.replace(".", "/mods/");
|
|
QVariant conf = getValue(modSettings, path);
|
|
|
|
if(conf.isNull())
|
|
{
|
|
settings["active"] = !local.value("keepDisabled").toBool();
|
|
}
|
|
else
|
|
{
|
|
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())
|
|
{
|
|
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()))
|
|
settings["active"] = false;
|
|
}
|
|
|
|
for(auto entry : repositories)
|
|
{
|
|
QVariant repoVal = getValue(entry, path);
|
|
if(repoVal.isValid())
|
|
{
|
|
auto repoValMap = repoVal.toMap();
|
|
if(::isCompatible(repoValMap["compatibility"].toMap()))
|
|
{
|
|
if(repo.empty()
|
|
|| CModVersion::fromString(repo["version"].toString().toStdString())
|
|
< CModVersion::fromString(repoValMap["version"].toString().toStdString()))
|
|
{
|
|
//take valid download link, screenshots and mod size before assignment
|
|
auto download = repo.value("download");
|
|
auto screenshots = repo.value("screenshots");
|
|
auto size = repo.value("downloadSize");
|
|
repo = repoValMap;
|
|
if(repo.value("download").isNull())
|
|
{
|
|
repo["download"] = download;
|
|
if(repo.value("screenshots").isNull()) //taking screenshot from the downloadable version
|
|
repo["screenshots"] = screenshots;
|
|
}
|
|
if(repo.value("downloadSize").isNull())
|
|
repo["downloadSize"] = size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return CModEntry(repo, local, settings, modname);
|
|
}
|
|
|
|
bool CModList::hasMod(QString modname) const
|
|
{
|
|
if(localModList.contains(modname))
|
|
return true;
|
|
|
|
for(auto entry : repositories)
|
|
if(entry.contains(modname))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
QStringList CModList::getRequirements(QString modname)
|
|
{
|
|
QStringList ret;
|
|
|
|
if(hasMod(modname))
|
|
{
|
|
auto mod = getMod(modname);
|
|
|
|
for(auto entry : mod.getDependencies())
|
|
ret += getRequirements(entry.toLower());
|
|
}
|
|
ret += modname;
|
|
|
|
return ret;
|
|
}
|
|
|
|
QVector<QString> CModList::getModList() const
|
|
{
|
|
QSet<QString> knownMods;
|
|
QVector<QString> modList;
|
|
for(auto repo : repositories)
|
|
{
|
|
for(auto it = repo.begin(); it != repo.end(); it++)
|
|
{
|
|
knownMods.insert(it.key().toLower());
|
|
}
|
|
}
|
|
for(auto it = localModList.begin(); it != localModList.end(); it++)
|
|
{
|
|
knownMods.insert(it.key().toLower());
|
|
}
|
|
|
|
for(auto entry : knownMods)
|
|
{
|
|
modList.push_back(entry);
|
|
}
|
|
return modList;
|
|
}
|
|
|
|
QVector<QString> CModList::getChildren(QString parent) const
|
|
{
|
|
QVector<QString> children;
|
|
|
|
int depth = parent.count('.') + 1;
|
|
for(const QString & mod : getModList())
|
|
{
|
|
if(mod.count('.') == depth && mod.startsWith(parent))
|
|
children.push_back(mod);
|
|
}
|
|
return children;
|
|
}
|