mirror of
https://github.com/ModOrganizer2/modorganizer.git
synced 2026-07-27 13:58:24 -07:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f39ef200 | ||
|
|
acd201157d | ||
|
|
f11925ab88 | ||
|
|
2395c13c25 | ||
|
|
cc92f6cf88 | ||
|
|
193442e773 | ||
|
|
a4f6298111 | ||
|
|
d3b647ab2b | ||
|
|
6d08d434a8 | ||
|
|
3d8bfdd1d4 | ||
|
|
fff03d34e2 | ||
|
|
88c386d74d | ||
|
|
c43535f5bc | ||
|
|
3d18f0cd38 | ||
|
|
a11617ff65 | ||
|
|
1871f32a8d | ||
|
|
fa82d1cca1 | ||
|
|
9ee4c5afe1 | ||
|
|
a1d199134d | ||
|
|
16423b2682 | ||
|
|
5adb1ec43d | ||
|
|
4472df8656 | ||
|
|
45c017de2f | ||
|
|
b350afee9e | ||
|
|
b3d27472cf | ||
|
|
e1932b34ea | ||
|
|
70a41a9b27 | ||
|
|
20aa98b511 | ||
|
|
783c05478f | ||
|
|
0315a88bd8 | ||
|
|
026fec4468 |
@@ -15,6 +15,6 @@ jobs:
|
||||
with:
|
||||
qt-modules: qtpositioning qtwebchannel qtwebengine qtwebsockets
|
||||
mo2-third-parties:
|
||||
7z zlib fmt gtest libbsarch libloot openssl libffi bzip2 python lz4 spdlog
|
||||
boost boost-di sip pyqt pybind11 ss licenses explorerpp usvfs
|
||||
mo2-dependencies: cmake_common uibase githubpp bsatk esptk archive lootcli game_gamebryo
|
||||
7z zlib gtest libbsarch libloot openssl bzip2 python lz4 spdlog
|
||||
boost boost-di sip pyqt pybind11 ss licenses explorerpp DirectXTex
|
||||
mo2-dependencies: usvfs cmake_common uibase githubpp bsatk esptk archive lootcli game_gamebryo
|
||||
|
||||
@@ -10,8 +10,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run clang-format
|
||||
uses: jidicula/clang-format-action@v4.11.0
|
||||
- name: Check format
|
||||
uses: ModOrganizer2/check-formatting-action@master
|
||||
with:
|
||||
clang-format-version: "15"
|
||||
check-path: "."
|
||||
exclude-regex: "third-party"
|
||||
|
||||
+10
-2
@@ -7,12 +7,18 @@ mo2_configure_executable(organizer
|
||||
EXTRA_TRANSLATIONS ${MO2_SUPER_PATH}/game_gamebryo/src ${MO2_UIBASE_PATH}/src
|
||||
PRIVATE_DEPENDS
|
||||
uibase githubpp bsatk esptk archive usvfs lootcli boost::program_options
|
||||
Qt::WebEngineWidgets Qt::WebSockets)
|
||||
DirectXTex libbsarch Qt::WebEngineWidgets Qt::WebSockets)
|
||||
target_link_libraries(organizer PUBLIC Shlwapi)
|
||||
target_include_directories(organizer PUBLIC ${DDS_ROOT})
|
||||
mo2_install_target(organizer)
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt5"
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt6"
|
||||
DESTINATION bin/dlls
|
||||
CONFIGURATIONS Release RelWithDebInfo
|
||||
RENAME dlls.manifest)
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.debug.qt6"
|
||||
DESTINATION bin/dlls
|
||||
CONFIGURATIONS Debug
|
||||
RENAME dlls.manifest)
|
||||
|
||||
install(DIRECTORY
|
||||
@@ -55,6 +61,7 @@ mo2_add_filter(NAME src/core GROUPS
|
||||
nexusinterface
|
||||
nxmaccessmanager
|
||||
organizercore
|
||||
game_features
|
||||
plugincontainer
|
||||
apiuseraccount
|
||||
processrunner
|
||||
@@ -196,6 +203,7 @@ mo2_add_filter(NAME src/profiles GROUPS
|
||||
|
||||
mo2_add_filter(NAME src/proxies GROUPS
|
||||
downloadmanagerproxy
|
||||
gamefeaturesproxy
|
||||
modlistproxy
|
||||
organizerproxy
|
||||
pluginlistproxy
|
||||
|
||||
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
using namespace MOBase;
|
||||
|
||||
ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets,
|
||||
QWidget* parent)
|
||||
: TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog)
|
||||
|
||||
@@ -24,9 +24,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "tutorabledialog.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QString;
|
||||
class QWidget;
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
#include <set>
|
||||
|
||||
@@ -49,7 +48,7 @@ public:
|
||||
* @param missingPlugins a map containing missing plugins that need to be activated
|
||||
* @param parent ... Defaults to 0.
|
||||
**/
|
||||
explicit ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets,
|
||||
explicit ActivateModsDialog(MOBase::SaveGameInfo::MissingAssets const& missingAssets,
|
||||
QWidget* parent = 0);
|
||||
~ActivateModsDialog();
|
||||
|
||||
|
||||
+11
-15
@@ -74,7 +74,7 @@ void CategoryFactory::loadCategories()
|
||||
bool ok = false;
|
||||
int temp = iter->toInt(&ok);
|
||||
if (!ok) {
|
||||
log::error(tr("invalid category id {}").toStdString(), iter->constData());
|
||||
log::error(tr("invalid category id {0}"), iter->constData());
|
||||
}
|
||||
nexusCats.push_back(NexusCategory("Unknown", temp));
|
||||
}
|
||||
@@ -84,8 +84,7 @@ void CategoryFactory::loadCategories()
|
||||
int id = cells[0].toInt(&cell0Ok);
|
||||
int parentID = cells[3].trimmed().toInt(&cell3Ok);
|
||||
if (!cell0Ok || !cell3Ok) {
|
||||
log::error(tr("invalid category line {}: {}").toStdString(), lineNum,
|
||||
line.constData());
|
||||
log::error(tr("invalid category line {0}: {1}"), lineNum, line.constData());
|
||||
}
|
||||
addCategory(id, QString::fromUtf8(cells[1].constData()), nexusCats, parentID);
|
||||
} else if (cells.count() == 3) {
|
||||
@@ -94,14 +93,13 @@ void CategoryFactory::loadCategories()
|
||||
int id = cells[0].toInt(&cell0Ok);
|
||||
int parentID = cells[2].trimmed().toInt(&cell3Ok);
|
||||
if (!cell0Ok || !cell3Ok) {
|
||||
log::error(tr("invalid category line {}: {}").toStdString(), lineNum,
|
||||
line.constData());
|
||||
log::error(tr("invalid category line {0}: {1}"), lineNum, line.constData());
|
||||
}
|
||||
|
||||
addCategory(id, QString::fromUtf8(cells[1].constData()),
|
||||
std::vector<NexusCategory>(), parentID);
|
||||
} else {
|
||||
log::error(tr("invalid category line {}: {} ({} cells)").toStdString(), lineNum,
|
||||
log::error(tr("invalid category line {0}: {1} ({2} cells)"), lineNum,
|
||||
line.constData(), cells.count());
|
||||
}
|
||||
}
|
||||
@@ -122,19 +120,17 @@ void CategoryFactory::loadCategories()
|
||||
bool ok = false;
|
||||
int nexID = nexCells[2].toInt(&ok);
|
||||
if (!ok) {
|
||||
log::error(tr("invalid nexus ID {}").toStdString(),
|
||||
nexCells[2].constData());
|
||||
log::error(tr("invalid nexus ID {}"), nexCells[2].constData());
|
||||
}
|
||||
int catID = nexCells[0].toInt(&ok);
|
||||
if (!ok) {
|
||||
log::error(tr("invalid category id {}").toStdString(),
|
||||
nexCells[0].constData());
|
||||
log::error(tr("invalid category id {}"), nexCells[0].constData());
|
||||
}
|
||||
m_NexusMap.insert_or_assign(nexID, NexusCategory(nexName, nexID));
|
||||
m_NexusMap.at(nexID).setCategoryID(catID);
|
||||
} else {
|
||||
log::error(tr("invalid nexus category line {}: {} ({} cells)").toStdString(),
|
||||
lineNum, nexLine.constData(), nexCells.count());
|
||||
log::error(tr("invalid nexus category line {0}: {1} ({2} cells)"), lineNum,
|
||||
nexLine.constData(), nexCells.count());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,7 +390,7 @@ bool CategoryFactory::isDescendantOfImpl(int id, int parentID,
|
||||
return isDescendantOfImpl(m_Categories[index].parentID(), parentID, seen);
|
||||
}
|
||||
} else {
|
||||
log::warn(tr("{} is no valid category id").toStdString(), id);
|
||||
log::warn(tr("{} is no valid category id"), id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -513,11 +509,11 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const
|
||||
auto result = m_NexusMap.find(nexusID);
|
||||
if (result != m_NexusMap.end()) {
|
||||
if (m_IDMap.count(result->second.categoryID())) {
|
||||
log::debug(tr("nexus category id {} maps to internal {}").toStdString(), nexusID,
|
||||
log::debug(tr("nexus category id {0} maps to internal {1}"), nexusID,
|
||||
m_IDMap.at(result->second.categoryID()));
|
||||
return m_IDMap.at(result->second.categoryID());
|
||||
}
|
||||
}
|
||||
log::debug(tr("nexus category id {} not mapped").toStdString(), nexusID);
|
||||
log::debug(tr("nexus category id {} not mapped"), nexusID);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
@@ -148,8 +148,9 @@ void CategoriesDialog::commitChanges()
|
||||
|
||||
for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) {
|
||||
int index = ui->categoriesTable->verticalHeader()->logicalIndex(i);
|
||||
QVariantList nexusData =
|
||||
ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList();
|
||||
QVariantList nexusData;
|
||||
if (ui->categoriesTable->item(index, 3) != nullptr)
|
||||
nexusData = ui->categoriesTable->item(index, 3)->data(Qt::UserRole).toList();
|
||||
std::vector<CategoryFactory::NexusCategory> nexusCats;
|
||||
for (auto nexusCat : nexusData) {
|
||||
nexusCats.push_back(CategoryFactory::NexusCategory(
|
||||
|
||||
@@ -296,7 +296,8 @@ void GamePage::select(IPluginGame* game, const QString& dir)
|
||||
// ask the user
|
||||
|
||||
const auto path = QFileDialog::getExistingDirectory(
|
||||
&m_dlg, QObject::tr("Find game installation for %1").arg(game->gameName()));
|
||||
&m_dlg,
|
||||
QObject::tr("Find game installation for %1").arg(game->displayGameName()));
|
||||
|
||||
if (path.isEmpty()) {
|
||||
// cancelled
|
||||
@@ -388,7 +389,7 @@ void GamePage::warnUnrecognized(const QString& path)
|
||||
// put the list of supported games in the details textbox
|
||||
QString supportedGames;
|
||||
for (auto* game : sortedGamePlugins()) {
|
||||
supportedGames += game->gameName() + "\n";
|
||||
supportedGames += game->displayGameName() + "\n";
|
||||
}
|
||||
|
||||
QMessageBox dlg(&m_dlg);
|
||||
@@ -417,7 +418,7 @@ std::vector<IPluginGame*> GamePage::sortedGamePlugins() const
|
||||
|
||||
// natsort
|
||||
std::sort(v.begin(), v.end(), [](auto* a, auto* b) {
|
||||
return (naturalCompare(a->gameName(), b->gameName()) < 0);
|
||||
return (naturalCompare(a->displayGameName(), b->displayGameName()) < 0);
|
||||
});
|
||||
|
||||
return v;
|
||||
@@ -469,7 +470,7 @@ void GamePage::updateButton(Game* g)
|
||||
return;
|
||||
}
|
||||
|
||||
g->button->setText(g->game->gameName().replace("&", "&&"));
|
||||
g->button->setText(g->game->displayGameName().replace("&", "&&"));
|
||||
g->button->setIcon(g->game->gameIcon());
|
||||
|
||||
if (g->installed) {
|
||||
@@ -570,7 +571,8 @@ void GamePage::fillList()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_filter.matches(g->game->gameName())) {
|
||||
if (!m_filter.matches(g->game->gameName()) &&
|
||||
!m_filter.matches(g->game->displayGameName())) {
|
||||
// filtered out
|
||||
continue;
|
||||
}
|
||||
@@ -667,9 +669,10 @@ bool GamePage::confirmMicrosoftStore(const QString& path, IPluginGame* game)
|
||||
"Organizer"
|
||||
" and will not work properly.")
|
||||
.arg(path))
|
||||
.button({game ? QObject::tr("Use this folder for %1").arg(game->gameName())
|
||||
: QObject::tr("Use this folder"),
|
||||
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
|
||||
.button(
|
||||
{game ? QObject::tr("Use this folder for %1").arg(game->displayGameName())
|
||||
: QObject::tr("Use this folder"),
|
||||
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
|
||||
.button({QObject::tr("Cancel"), QMessageBox::Cancel})
|
||||
.exec();
|
||||
|
||||
@@ -688,8 +691,8 @@ bool GamePage::confirmUnknown(const QString& path, IPluginGame* game)
|
||||
"bold;\">%2</span> or "
|
||||
"for any other game Mod Organizer can manage.")
|
||||
.arg(path)
|
||||
.arg(game->gameName()))
|
||||
.button({QObject::tr("Use this folder for %1").arg(game->gameName()),
|
||||
.arg(game->displayGameName()))
|
||||
.button({QObject::tr("Use this folder for %1").arg(game->displayGameName()),
|
||||
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
|
||||
.button({QObject::tr("Cancel"), QMessageBox::Cancel})
|
||||
.exec();
|
||||
@@ -711,11 +714,12 @@ IPluginGame* GamePage::confirmOtherGame(const QString& path, IPluginGame* select
|
||||
"not "
|
||||
"<span style=\"white-space: nowrap; font-weight: bold;\">%3</span>.")
|
||||
.arg(path)
|
||||
.arg(guessedGame->gameName())
|
||||
.arg(selectedGame->gameName()))
|
||||
.button({QObject::tr("Manage %1 instead").arg(guessedGame->gameName()),
|
||||
.arg(guessedGame->displayGameName())
|
||||
.arg(selectedGame->displayGameName()))
|
||||
.button({QObject::tr("Manage %1 instead").arg(guessedGame->displayGameName()),
|
||||
QMessageBox::Ok})
|
||||
.button({QObject::tr("Use this folder for %1").arg(selectedGame->gameName()),
|
||||
.button({QObject::tr("Use this folder for %1")
|
||||
.arg(selectedGame->displayGameName()),
|
||||
QObject::tr("I know what I'm doing"), QMessageBox::Ignore})
|
||||
.button({QObject::tr("Cancel"), QMessageBox::Cancel})
|
||||
.exec();
|
||||
|
||||
+7
-7
@@ -16,13 +16,13 @@ QString UnmanagedModName();
|
||||
|
||||
DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent,
|
||||
Ui::MainWindow* mwui)
|
||||
: m_core(core), m_pluginContainer(pc),
|
||||
m_parent(parent), ui{mwui->tabWidget,
|
||||
mwui->dataTab,
|
||||
mwui->dataTabRefresh,
|
||||
mwui->dataTree,
|
||||
mwui->dataTabShowOnlyConflicts,
|
||||
mwui->dataTabShowFromArchives},
|
||||
: m_core(core), m_pluginContainer(pc), m_parent(parent),
|
||||
ui{mwui->tabWidget,
|
||||
mwui->dataTab,
|
||||
mwui->dataTabRefresh,
|
||||
mwui->dataTree,
|
||||
mwui->dataTabShowOnlyConflicts,
|
||||
mwui->dataTabShowFromArchives},
|
||||
m_needUpdate(true)
|
||||
{
|
||||
m_filetree.reset(new FileTree(core, m_pluginContainer, ui.tree));
|
||||
|
||||
+16
-16
@@ -22,9 +22,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "shared/filesorigin.h"
|
||||
|
||||
#include "envfs.h"
|
||||
#include "game_features.h"
|
||||
#include "iplugingame.h"
|
||||
#include "modinfo.h"
|
||||
#include "modinfodialogfwd.h"
|
||||
#include "organizercore.h"
|
||||
#include "report.h"
|
||||
#include "settings.h"
|
||||
#include "shared/util.h"
|
||||
@@ -138,7 +140,7 @@ void dumpStats(std::vector<DirectoryStats>& stats)
|
||||
|
||||
if (run == 0) {
|
||||
std::ofstream out(file, std::ios::out | std::ios::trunc);
|
||||
out << fmt::format("what,run,{}", DirectoryStats::csvHeader()) << "\n";
|
||||
out << std::format("what,run,{}", DirectoryStats::csvHeader()) << "\n";
|
||||
}
|
||||
|
||||
std::sort(stats.begin(), stats.end(), [](auto&& a, auto&& b) {
|
||||
@@ -150,17 +152,17 @@ void dumpStats(std::vector<DirectoryStats>& stats)
|
||||
|
||||
DirectoryStats total;
|
||||
for (const auto& s : stats) {
|
||||
out << fmt::format("{},{},{}", s.mod, run, s.toCsv()) << "\n";
|
||||
out << std::format("{},{},{}", s.mod, run, s.toCsv()) << "\n";
|
||||
total += s;
|
||||
}
|
||||
|
||||
out << fmt::format("total,{},{}", run, total.toCsv()) << "\n";
|
||||
out << std::format("total,{},{}", run, total.toCsv()) << "\n";
|
||||
|
||||
++run;
|
||||
}
|
||||
|
||||
DirectoryRefresher::DirectoryRefresher(std::size_t threadCount)
|
||||
: m_threadCount(threadCount), m_lastFileCount(0)
|
||||
DirectoryRefresher::DirectoryRefresher(OrganizerCore* core, std::size_t threadCount)
|
||||
: m_Core(*core), m_threadCount(threadCount), m_lastFileCount(0)
|
||||
{}
|
||||
|
||||
DirectoryEntry* DirectoryRefresher::stealDirectoryStructure()
|
||||
@@ -204,11 +206,9 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry* root,
|
||||
const QString& directory,
|
||||
const QStringList& archives)
|
||||
{
|
||||
const IPluginGame* game = qApp->property("managed_game").value<IPluginGame*>();
|
||||
|
||||
QStringList loadOrder;
|
||||
|
||||
GamePlugins* gamePlugins = game->feature<GamePlugins>();
|
||||
auto gamePlugins = m_Core.gameFeatures().gameFeature<GamePlugins>();
|
||||
if (gamePlugins) {
|
||||
loadOrder = gamePlugins->getLoadOrder();
|
||||
}
|
||||
@@ -320,6 +320,7 @@ void DirectoryRefresher::addModToStructure(DirectoryEntry* directoryStructure,
|
||||
|
||||
struct ModThread
|
||||
{
|
||||
GameFeatures* gameFeatures;
|
||||
DirectoryRefreshProgress* progress = nullptr;
|
||||
DirectoryEntry* ds = nullptr;
|
||||
std::wstring modName;
|
||||
@@ -355,10 +356,8 @@ struct ModThread
|
||||
ds->addFromOrigin(walker, modName, path, prio, *stats);
|
||||
|
||||
if (Settings::instance().archiveParsing()) {
|
||||
const IPluginGame* game = qApp->property("managed_game").value<IPluginGame*>();
|
||||
|
||||
QStringList loadOrder;
|
||||
GamePlugins* gamePlugins = game->feature<GamePlugins>();
|
||||
auto gamePlugins = gameFeatures->gameFeature<GamePlugins>();
|
||||
if (gamePlugins) {
|
||||
loadOrder = gamePlugins->getLoadOrder();
|
||||
}
|
||||
@@ -420,11 +419,12 @@ void DirectoryRefresher::addMultipleModsFilesToStructure(
|
||||
} else {
|
||||
auto& mt = g_threads.request();
|
||||
|
||||
mt.progress = progress;
|
||||
mt.ds = directoryStructure;
|
||||
mt.modName = e.modName.toStdWString();
|
||||
mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
|
||||
mt.prio = prio;
|
||||
mt.gameFeatures = &m_Core.gameFeatures();
|
||||
mt.progress = progress;
|
||||
mt.ds = directoryStructure;
|
||||
mt.modName = e.modName.toStdWString();
|
||||
mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
|
||||
mt.prio = prio;
|
||||
|
||||
mt.archives.clear();
|
||||
for (auto&& a : e.archives) {
|
||||
|
||||
@@ -30,6 +30,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
class OrganizerCore;
|
||||
|
||||
/**
|
||||
* @brief used to asynchronously generate the virtual view of the combined data
|
||||
*directory
|
||||
@@ -54,7 +56,7 @@ public:
|
||||
int priority;
|
||||
};
|
||||
|
||||
DirectoryRefresher(std::size_t threadCount);
|
||||
DirectoryRefresher(OrganizerCore* core, std::size_t threadCount);
|
||||
|
||||
/**
|
||||
* @brief retrieve the updated directory structure
|
||||
@@ -146,6 +148,8 @@ signals:
|
||||
void refreshed();
|
||||
|
||||
private:
|
||||
OrganizerCore& m_Core;
|
||||
|
||||
std::vector<EntryInfo> m_Mods;
|
||||
std::set<QString> m_EnabledArchives;
|
||||
std::unique_ptr<MOShared::DirectoryEntry> m_Root;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
|
||||
<file name="7z.dll"/>
|
||||
<file name="archive.dll"/>
|
||||
<file name="d3dcompiler_47.dll"/>
|
||||
<file name="libbsarch.dll"/>
|
||||
<file name="libcrypto-1_1-x64.dll"/>
|
||||
<file name="libEGL.dll"/>
|
||||
<file name="libGLESV2.dll"/>
|
||||
<file name="liblz4.dll"/>
|
||||
<file name="libssl-1_1-x64.dll"/>
|
||||
<file name="opengl32sw.dll"/>
|
||||
<file name="Qt6Concurrent.dll"/>
|
||||
<file name="Qt6Core.dll"/>
|
||||
<file name="Qt6Core5Compat.dll"/>
|
||||
<file name="Qt6Gui.dll"/>
|
||||
<file name="Qt6Network.dll"/>
|
||||
<file name="Qt6OpenGL.dll"/>
|
||||
<file name="Qt6OpenGLWidgets.dll"/>
|
||||
<file name="Qt6Positioning.dll"/>
|
||||
<file name="Qt6PrintSupport.dll"/>
|
||||
<file name="Qt6Qml.dll"/>
|
||||
<file name="Qt6QmlLocalStorage.dll"/>
|
||||
<file name="Qt6QmlModels.dll"/>
|
||||
<file name="Qt6QmlWorkerScript.dll"/>
|
||||
<file name="Qt6QmlXmlListModel.dll"/>
|
||||
<file name="Qt6Quick.dll"/>
|
||||
<file name="Qt6QuickControls2.dll"/>
|
||||
<file name="Qt6QuickControls2Impl.dll"/>
|
||||
<file name="Qt6QuickDialogs2.dll"/>
|
||||
<file name="Qt6QuickDialogs2QuickImpl.dll"/>
|
||||
<file name="Qt6QuickDialogs2Utils.dll"/>
|
||||
<file name="Qt6QuickLayouts.dll"/>
|
||||
<file name="Qt6QuickParticles.dll"/>
|
||||
<file name="Qt6QuickShapes.dll"/>
|
||||
<file name="Qt6QuickTemplates2.dll"/>
|
||||
<file name="Qt6QuickWidgets.dll"/>
|
||||
<file name="Qt6Sql.dll"/>
|
||||
<file name="Qt6Svg.dll"/>
|
||||
<file name="Qt6WebChannel.dll"/>
|
||||
<file name="Qt6WebEngineCore.dll"/>
|
||||
<file name="Qt6WebEngineWidgets.dll"/>
|
||||
<file name="Qt6WebSockets.dll"/>
|
||||
<file name="Qt6Widgets.dll"/>
|
||||
</assembly>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86" />
|
||||
<file name="7z.dll" />
|
||||
<file name="archive.dll" />
|
||||
<file name="d3dcompiler_47.dll" />
|
||||
<file name="libbsarch.dll" />
|
||||
<file name="libcrypto-3-x64.dll" />
|
||||
<file name="libEGL.dll" />
|
||||
<file name="libGLESV2.dll" />
|
||||
<file name="liblz4.dll" />
|
||||
<file name="libssl-3-x64.dll" />
|
||||
<file name="opengl32sw.dll" />
|
||||
<file name="Qt6Concurrentd.dll" />
|
||||
<file name="Qt6Cored.dll" />
|
||||
<file name="Qt6Core5Compatd.dll" />
|
||||
<file name="Qt6Guid.dll" />
|
||||
<file name="Qt6Networkd.dll" />
|
||||
<file name="Qt6OpenGLd.dll" />
|
||||
<file name="Qt6OpenGLWidgetsd.dll" />
|
||||
<file name="Qt6Positioningd.dll" />
|
||||
<file name="Qt6PrintSupportd.dll" />
|
||||
<file name="Qt6Qmld.dll" />
|
||||
<file name="Qt6QmlLocalStoraged.dll" />
|
||||
<file name="Qt6QmlModelsd.dll" />
|
||||
<file name="Qt6QmlWorkerScriptd.dll" />
|
||||
<file name="Qt6QmlXmlListModeld.dll" />
|
||||
<file name="Qt6Quickd.dll" />
|
||||
<file name="Qt6QuickControls2d.dll" />
|
||||
<file name="Qt6QuickControls2Impld.dll" />
|
||||
<file name="Qt6QuickDialogs2d.dll" />
|
||||
<file name="Qt6QuickDialogs2QuickImpld.dll" />
|
||||
<file name="Qt6QuickDialogs2Utilsd.dll" />
|
||||
<file name="Qt6QuickLayoutsd.dll" />
|
||||
<file name="Qt6QuickParticlesd.dll" />
|
||||
<file name="Qt6QuickShapesd.dll" />
|
||||
<file name="Qt6QuickTemplates2d.dll" />
|
||||
<file name="Qt6QuickWidgetsd.dll" />
|
||||
<file name="Qt6Sqld.dll" />
|
||||
<file name="Qt6Svgd.dll" />
|
||||
<file name="Qt6WebChanneld.dll" />
|
||||
<file name="Qt6WebEngineCored.dll" />
|
||||
<file name="Qt6WebEngineWidgetsd.dll" />
|
||||
<file name="Qt6WebSocketsd.dll" />
|
||||
<file name="Qt6Widgetsd.dll" />
|
||||
</assembly>
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
|
||||
<file name="7z.dll"/>
|
||||
<file name="archive.dll"/>
|
||||
<file name="d3dcompiler_47.dll"/>
|
||||
<file name="libbsarch.dll"/>
|
||||
<file name="libcrypto-1_1-x64.dll"/>
|
||||
<file name="libEGL.dll"/>
|
||||
<file name="libGLESV2.dll"/>
|
||||
<file name="liblz4.dll"/>
|
||||
<file name="libssl-1_1-x64.dll"/>
|
||||
<file name="opengl32sw.dll"/>
|
||||
<file name="Qt6Concurrent.dll"/>
|
||||
<file name="Qt6Core.dll"/>
|
||||
<file name="Qt6Core5Compat.dll"/>
|
||||
<file name="Qt6Gui.dll"/>
|
||||
<file name="Qt6Network.dll"/>
|
||||
<file name="Qt6OpenGL.dll"/>
|
||||
<file name="Qt6OpenGLWidgets.dll"/>
|
||||
<file name="Qt6Positioning.dll"/>
|
||||
<file name="Qt6PrintSupport.dll"/>
|
||||
<file name="Qt6Qml.dll"/>
|
||||
<file name="Qt6QmlLocalStorage.dll"/>
|
||||
<file name="Qt6QmlModels.dll"/>
|
||||
<file name="Qt6QmlWorkerScript.dll"/>
|
||||
<file name="Qt6QmlXmlListModel.dll"/>
|
||||
<file name="Qt6Quick.dll"/>
|
||||
<file name="Qt6QuickControls2.dll"/>
|
||||
<file name="Qt6QuickControls2Impl.dll"/>
|
||||
<file name="Qt6QuickDialogs2.dll"/>
|
||||
<file name="Qt6QuickDialogs2QuickImpl.dll"/>
|
||||
<file name="Qt6QuickDialogs2Utils.dll"/>
|
||||
<file name="Qt6QuickLayouts.dll"/>
|
||||
<file name="Qt6QuickParticles.dll"/>
|
||||
<file name="Qt6QuickShapes.dll"/>
|
||||
<file name="Qt6QuickTemplates2.dll"/>
|
||||
<file name="Qt6QuickWidgets.dll"/>
|
||||
<file name="Qt6Sql.dll"/>
|
||||
<file name="Qt6Svg.dll"/>
|
||||
<file name="Qt6WebChannel.dll"/>
|
||||
<file name="Qt6WebEngineCore.dll"/>
|
||||
<file name="Qt6WebEngineWidgets.dll"/>
|
||||
<file name="Qt6WebSockets.dll"/>
|
||||
<file name="Qt6Widgets.dll"/>
|
||||
</assembly>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86" />
|
||||
<file name="7z.dll" />
|
||||
<file name="archive.dll" />
|
||||
<file name="d3dcompiler_47.dll" />
|
||||
<file name="libbsarch.dll" />
|
||||
<file name="libcrypto-3-x64.dll" />
|
||||
<file name="libEGL.dll" />
|
||||
<file name="libGLESV2.dll" />
|
||||
<file name="liblz4.dll" />
|
||||
<file name="libssl-3-x64.dll" />
|
||||
<file name="opengl32sw.dll" />
|
||||
<file name="Qt6Concurrent.dll" />
|
||||
<file name="Qt6Core.dll" />
|
||||
<file name="Qt6Core5Compat.dll" />
|
||||
<file name="Qt6Gui.dll" />
|
||||
<file name="Qt6Network.dll" />
|
||||
<file name="Qt6OpenGL.dll" />
|
||||
<file name="Qt6OpenGLWidgets.dll" />
|
||||
<file name="Qt6Positioning.dll" />
|
||||
<file name="Qt6PrintSupport.dll" />
|
||||
<file name="Qt6Qml.dll" />
|
||||
<file name="Qt6QmlLocalStorage.dll" />
|
||||
<file name="Qt6QmlModels.dll" />
|
||||
<file name="Qt6QmlWorkerScript.dll" />
|
||||
<file name="Qt6QmlXmlListModel.dll" />
|
||||
<file name="Qt6Quick.dll" />
|
||||
<file name="Qt6QuickControls2.dll" />
|
||||
<file name="Qt6QuickControls2Impl.dll" />
|
||||
<file name="Qt6QuickDialogs2.dll" />
|
||||
<file name="Qt6QuickDialogs2QuickImpl.dll" />
|
||||
<file name="Qt6QuickDialogs2Utils.dll" />
|
||||
<file name="Qt6QuickLayouts.dll" />
|
||||
<file name="Qt6QuickParticles.dll" />
|
||||
<file name="Qt6QuickShapes.dll" />
|
||||
<file name="Qt6QuickTemplates2.dll" />
|
||||
<file name="Qt6QuickWidgets.dll" />
|
||||
<file name="Qt6Sql.dll" />
|
||||
<file name="Qt6Svg.dll" />
|
||||
<file name="Qt6WebChannel.dll" />
|
||||
<file name="Qt6WebEngineCore.dll" />
|
||||
<file name="Qt6WebEngineWidgets.dll" />
|
||||
<file name="Qt6WebSockets.dll" />
|
||||
<file name="Qt6Widgets.dll" />
|
||||
</assembly>
|
||||
+8
-4
@@ -1,8 +1,12 @@
|
||||
#include "envshell.h"
|
||||
|
||||
#include <format>
|
||||
|
||||
#include <log.h>
|
||||
#include <utility.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include "envshell.h"
|
||||
|
||||
namespace env
|
||||
{
|
||||
|
||||
@@ -16,7 +20,7 @@ class MenuFailed : public std::runtime_error
|
||||
public:
|
||||
MenuFailed(HRESULT r, const std::string& what)
|
||||
: runtime_error(
|
||||
fmt::format("{}, {}", what,
|
||||
std::format("{}, {}", what,
|
||||
QString::fromStdWString(formatSystemMessage(r)).toStdString()))
|
||||
{}
|
||||
};
|
||||
@@ -478,7 +482,7 @@ void ShellMenu::invoke(const QPoint& p, int cmd)
|
||||
const auto r = m_cm->InvokeCommand((CMINVOKECOMMANDINFO*)&info);
|
||||
|
||||
if (FAILED(r)) {
|
||||
throw MenuFailed(r, fmt::format("InvokeCommand failed, verb={}", cmd));
|
||||
throw MenuFailed(r, std::format("InvokeCommand failed, verb={}", cmd));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,7 +572,7 @@ void ShellMenuCollection::exec(const QPoint& pos)
|
||||
}
|
||||
|
||||
if (!m_active) {
|
||||
log::debug("SMC: command {} selected without active submenu");
|
||||
log::debug("SMC: command {} selected without active submenu", cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+2
-6
@@ -246,16 +246,12 @@ void FileTree::activate(FileTreeItem* item)
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->isFromArchive()) {
|
||||
log::warn("cannot activate file from archive '{}'", item->filename());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto tryPreview = m_core.settings().interface().doubleClicksOpenPreviews();
|
||||
|
||||
if (tryPreview) {
|
||||
const QFileInfo fi(item->realPath());
|
||||
if (m_plugins.previewGenerator().previewSupported(fi.suffix().toLower())) {
|
||||
if (m_plugins.previewGenerator().previewSupported(fi.suffix().toLower(),
|
||||
item->isFromArchive())) {
|
||||
preview(item);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
#include "game_features.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "iplugingame.h"
|
||||
#include "log.h"
|
||||
|
||||
#include "bsainvalidation.h"
|
||||
#include "dataarchives.h"
|
||||
#include "gameplugins.h"
|
||||
#include "localsavegames.h"
|
||||
#include "organizercore.h"
|
||||
#include "plugincontainer.h"
|
||||
#include "savegameinfo.h"
|
||||
#include "scriptextender.h"
|
||||
#include "unmanagedmods.h"
|
||||
|
||||
using namespace MOBase;
|
||||
|
||||
// avoid updating features more than once every 100ms
|
||||
constexpr std::chrono::milliseconds UPDATE_QUEUE_TIMER{100};
|
||||
|
||||
// specific type index for checker and content that behaves differently
|
||||
const std::type_index ModDataCheckerIndex{typeid(ModDataChecker)};
|
||||
const std::type_index ModDataContentIndex{typeid(ModDataContent)};
|
||||
|
||||
class GameFeatures::CombinedModDataChecker : public ModDataChecker
|
||||
{
|
||||
std::vector<std::shared_ptr<ModDataChecker>> m_modDataCheckers;
|
||||
mutable std::shared_ptr<ModDataChecker> m_fixer = nullptr;
|
||||
|
||||
public:
|
||||
void setCheckers(std::vector<std::shared_ptr<ModDataChecker>> checkers)
|
||||
{
|
||||
m_modDataCheckers = std::move(checkers);
|
||||
m_fixer = nullptr;
|
||||
}
|
||||
|
||||
bool isValid() const { return !m_modDataCheckers.empty(); }
|
||||
|
||||
CheckReturn
|
||||
dataLooksValid(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
m_fixer = nullptr;
|
||||
|
||||
// go through the available mod-data checker, if any returns valid, we also
|
||||
// return valid, otherwise, return the first one that is fixable
|
||||
for (const auto& modDataChecker : m_modDataCheckers) {
|
||||
auto check = modDataChecker->dataLooksValid(fileTree);
|
||||
|
||||
switch (check) {
|
||||
case CheckReturn::FIXABLE:
|
||||
// only update fixer if there is not one with higher priority
|
||||
if (!m_fixer) {
|
||||
m_fixer = modDataChecker;
|
||||
}
|
||||
break;
|
||||
case CheckReturn::VALID:
|
||||
// clear fixer if one were found before and return VALID, not mandatory
|
||||
// but cleaner
|
||||
m_fixer = nullptr;
|
||||
return CheckReturn::VALID;
|
||||
case CheckReturn::INVALID:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return m_fixer ? CheckReturn::FIXABLE : CheckReturn::INVALID;
|
||||
}
|
||||
|
||||
std::shared_ptr<MOBase::IFileTree>
|
||||
fix(std::shared_ptr<MOBase::IFileTree> fileTree) const override
|
||||
{
|
||||
if (m_fixer) {
|
||||
return m_fixer->fix(fileTree);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
class GameFeatures::CombinedModDataContent : public ModDataContent
|
||||
{
|
||||
// store the ModDataContent and the offset to add to the content
|
||||
std::vector<std::pair<std::shared_ptr<ModDataContent>, int>> m_modDataContents;
|
||||
std::vector<Content> m_allContents;
|
||||
|
||||
public:
|
||||
bool isValid() const { return !m_modDataContents.empty(); }
|
||||
|
||||
void setContents(std::vector<std::shared_ptr<ModDataContent>> modDataContents)
|
||||
{
|
||||
m_modDataContents.clear();
|
||||
m_modDataContents.reserve(modDataContents.size());
|
||||
|
||||
m_allContents.clear();
|
||||
|
||||
// update all contents and offsets
|
||||
std::size_t offset = 0;
|
||||
for (auto& modDataContent : modDataContents) {
|
||||
|
||||
m_modDataContents.emplace_back(modDataContent, static_cast<int>(offset));
|
||||
|
||||
// add to the list of contents
|
||||
auto contents = modDataContent->getAllContents();
|
||||
m_allContents.insert(m_allContents.end(),
|
||||
std::make_move_iterator(contents.begin()),
|
||||
std::make_move_iterator(contents.end()));
|
||||
|
||||
// increase offset for next mod data content
|
||||
offset += contents.size();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Content> getAllContents() const override { return m_allContents; }
|
||||
|
||||
std::vector<int>
|
||||
getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const
|
||||
{
|
||||
std::vector<int> contentsFor;
|
||||
for (auto& modDataContent : m_modDataContents) {
|
||||
auto contentsForFrom = modDataContent.first->getContentsFor(fileTree);
|
||||
std::transform(contentsForFrom.begin(), contentsForFrom.end(),
|
||||
std::back_inserter(contentsFor),
|
||||
[offset = modDataContent.second](auto content) {
|
||||
return content + offset;
|
||||
});
|
||||
}
|
||||
|
||||
return contentsFor;
|
||||
}
|
||||
};
|
||||
|
||||
GameFeatures::GameFeatures(OrganizerCore* core, PluginContainer* plugins)
|
||||
: m_pluginContainer(*plugins),
|
||||
m_modDataChecker(std::make_unique<CombinedModDataChecker>()),
|
||||
m_modDataContent(std::make_unique<CombinedModDataContent>())
|
||||
{
|
||||
// can be nullptr since the plugin container can be initialized with a Core (e.g.,
|
||||
// on first MO2 start)
|
||||
if (core) {
|
||||
connect(core, &OrganizerCore::managedGameChanged, [this] {
|
||||
updateCurrentFeatures();
|
||||
});
|
||||
}
|
||||
|
||||
auto updateFeatures = [this] {
|
||||
QTimer::singleShot(UPDATE_QUEUE_TIMER, [this] {
|
||||
updateCurrentFeatures();
|
||||
});
|
||||
};
|
||||
|
||||
connect(plugins, &PluginContainer::pluginEnabled, updateFeatures);
|
||||
connect(plugins, &PluginContainer::pluginDisabled, updateFeatures);
|
||||
connect(plugins, &PluginContainer::pluginRegistered, updateFeatures);
|
||||
connect(plugins, &PluginContainer::pluginUnregistered,
|
||||
[this, updateFeatures](MOBase::IPlugin* plugin) {
|
||||
// remove features from the current plugin
|
||||
for (auto& [_, features] : m_allFeatures) {
|
||||
features.erase(std::remove_if(features.begin(), features.end(),
|
||||
[plugin](const auto& feature) {
|
||||
return feature.plugin() == plugin;
|
||||
}),
|
||||
features.end());
|
||||
}
|
||||
|
||||
// update current features
|
||||
updateFeatures();
|
||||
});
|
||||
}
|
||||
|
||||
GameFeatures::~GameFeatures() {}
|
||||
|
||||
GameFeatures::CombinedModDataChecker& GameFeatures::modDataChecker() const
|
||||
{
|
||||
return dynamic_cast<CombinedModDataChecker&>(*m_modDataChecker);
|
||||
}
|
||||
GameFeatures::CombinedModDataContent& GameFeatures::modDataContent() const
|
||||
{
|
||||
return dynamic_cast<CombinedModDataContent&>(*m_modDataContent);
|
||||
}
|
||||
|
||||
void GameFeatures::updateCurrentFeatures(std::type_index const& index)
|
||||
{
|
||||
auto& features = m_allFeatures[index];
|
||||
|
||||
m_currentFeatures[index].clear();
|
||||
|
||||
// this can occur when starting MO2, just wait for the next update
|
||||
if (!m_pluginContainer.managedGame()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& dataFeature : features) {
|
||||
|
||||
// registering plugin is disabled
|
||||
if (!m_pluginContainer.isEnabled(dataFeature.plugin())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// games does not match
|
||||
if (!dataFeature.games().isEmpty() &&
|
||||
!dataFeature.games().contains(m_pluginContainer.managedGame()->gameName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_currentFeatures[index].push_back(dataFeature.feature());
|
||||
}
|
||||
|
||||
// update mod data checker
|
||||
if (index == ModDataCheckerIndex) {
|
||||
auto& currentCheckers = m_currentFeatures[ModDataCheckerIndex];
|
||||
std::vector<std::shared_ptr<ModDataChecker>> checkers;
|
||||
checkers.reserve(currentCheckers.size());
|
||||
std::transform(currentCheckers.begin(), currentCheckers.end(),
|
||||
std::back_inserter(checkers), [](auto const& checker) {
|
||||
return std::dynamic_pointer_cast<ModDataChecker>(checker);
|
||||
});
|
||||
modDataChecker().setCheckers(std::move(checkers));
|
||||
emit modDataCheckerUpdated(gameFeature<ModDataChecker>().get());
|
||||
}
|
||||
|
||||
// update mod data content
|
||||
if (index == ModDataContentIndex) {
|
||||
auto& currentContents = m_currentFeatures[ModDataContentIndex];
|
||||
std::vector<std::shared_ptr<ModDataContent>> contents;
|
||||
contents.reserve(currentContents.size());
|
||||
std::transform(currentContents.begin(), currentContents.end(),
|
||||
std::back_inserter(contents), [](auto const& checker) {
|
||||
return std::dynamic_pointer_cast<ModDataContent>(checker);
|
||||
});
|
||||
modDataContent().setContents(std::move(contents));
|
||||
emit modDataContentUpdated(gameFeature<ModDataContent>().get());
|
||||
}
|
||||
}
|
||||
|
||||
void GameFeatures::updateCurrentFeatures()
|
||||
{
|
||||
// TODO: this completely refilters everything currently, which should be ok since
|
||||
// this should only be triggered by function that are uncommon (enabling/disabling
|
||||
// plugin or changing the game), it should be possible to filter more finely by
|
||||
// maintaining more information (e.g., only removing features from current when a
|
||||
// plugin is disabled or unregistered)
|
||||
//
|
||||
|
||||
for (auto& [info, _] : m_allFeatures) {
|
||||
updateCurrentFeatures(info);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameFeatures::registerGameFeature(MOBase::IPlugin* plugin,
|
||||
QStringList const& games,
|
||||
std::shared_ptr<MOBase::GameFeature> feature,
|
||||
int priority)
|
||||
{
|
||||
auto& features = m_allFeatures[feature->typeInfo()];
|
||||
|
||||
if (std::find_if(features.begin(), features.end(), [&feature](const auto& data) {
|
||||
return data.feature() == feature;
|
||||
}) != features.end()) {
|
||||
log::error("cannot register feature multiple time");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::decay_t<decltype(features)>::iterator it;
|
||||
if (dynamic_cast<IPluginGame*>(plugin)) {
|
||||
it = features.end();
|
||||
} else {
|
||||
it = std::lower_bound(features.begin(), features.end(), priority,
|
||||
[](const auto& feature, int priority) {
|
||||
return feature.priority() > priority;
|
||||
});
|
||||
}
|
||||
|
||||
features.emplace(it, feature, plugin, games, std::numeric_limits<int>::min());
|
||||
|
||||
// TODO: only update if relevant
|
||||
updateCurrentFeatures(feature->typeInfo());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// unregister game features
|
||||
//
|
||||
bool GameFeatures::unregisterGameFeature(std::shared_ptr<MOBase::GameFeature> feature)
|
||||
{
|
||||
bool removed = false;
|
||||
for (auto& [_, features] : m_allFeatures) {
|
||||
auto it =
|
||||
std::find_if(features.begin(), features.end(), [&feature](const auto& data) {
|
||||
return data.feature() == feature;
|
||||
});
|
||||
|
||||
// the feature can only exist for one kind of features and cannot be duplicated
|
||||
if (it != features.end()) {
|
||||
features.erase(it);
|
||||
removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
updateCurrentFeatures();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
int GameFeatures::unregisterGameFeatures(MOBase::IPlugin* plugin,
|
||||
std::type_info const& info)
|
||||
{
|
||||
auto& features = m_allFeatures[info];
|
||||
|
||||
const auto initialSize = features.size();
|
||||
|
||||
features.erase(std::remove_if(features.begin(), features.end(),
|
||||
[plugin](const auto& feature) {
|
||||
return feature.plugin() == plugin;
|
||||
}),
|
||||
features.end());
|
||||
|
||||
const int removed = features.size() - initialSize;
|
||||
|
||||
if (removed) {
|
||||
updateCurrentFeatures();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
std::shared_ptr<GameFeature> GameFeatures::gameFeature(std::type_info const& info) const
|
||||
{
|
||||
if (info == ModDataCheckerIndex) {
|
||||
return modDataChecker().isValid() ? m_modDataChecker : nullptr;
|
||||
}
|
||||
|
||||
if (info == ModDataContentIndex) {
|
||||
return modDataContent().isValid() ? m_modDataContent : nullptr;
|
||||
}
|
||||
|
||||
auto it = m_currentFeatures.find(info);
|
||||
if (it == m_currentFeatures.end() || it->second.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return it->second.front();
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
#ifndef MO2_GAME_FEATURES_H
|
||||
#define MO2_GAME_FEATURES_H
|
||||
|
||||
#include <any>
|
||||
#include <memory>
|
||||
#include <typeinfo>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "game_feature.h"
|
||||
#include "moddatachecker.h"
|
||||
#include "moddatacontent.h"
|
||||
|
||||
namespace MOBase
|
||||
{
|
||||
class IPlugin;
|
||||
class IPluginGame;
|
||||
} // namespace MOBase
|
||||
|
||||
class OrganizerCore;
|
||||
class PluginContainer;
|
||||
|
||||
/**
|
||||
* Class managing game features, either registered or from the game plugin.
|
||||
*/
|
||||
class GameFeatures : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
*
|
||||
*/
|
||||
GameFeatures(OrganizerCore* core, PluginContainer* plugins);
|
||||
|
||||
~GameFeatures();
|
||||
|
||||
// register game features
|
||||
//
|
||||
bool registerGameFeature(MOBase::IPlugin* plugin, QStringList const& games,
|
||||
std::shared_ptr<MOBase::GameFeature> feature, int priority);
|
||||
|
||||
// unregister game features
|
||||
//
|
||||
bool unregisterGameFeature(std::shared_ptr<MOBase::GameFeature> feature);
|
||||
int unregisterGameFeatures(MOBase::IPlugin* plugin, std::type_info const& info);
|
||||
|
||||
// retrieve a game feature
|
||||
//
|
||||
template <class T>
|
||||
std::shared_ptr<T> gameFeature() const
|
||||
{
|
||||
return std::dynamic_pointer_cast<T>(gameFeature(typeid(T)));
|
||||
}
|
||||
|
||||
signals:
|
||||
void modDataCheckerUpdated(const MOBase::ModDataChecker* check);
|
||||
void modDataContentUpdated(const MOBase::ModDataContent* check);
|
||||
|
||||
private:
|
||||
friend class GameFeaturesProxy;
|
||||
|
||||
class GameFeatureWithData
|
||||
{
|
||||
// feature
|
||||
std::shared_ptr<MOBase::GameFeature> m_feature;
|
||||
|
||||
// plugin that registered the feature
|
||||
MOBase::IPlugin* m_plugin;
|
||||
|
||||
// games this plugin applies to - empty list indicates all games
|
||||
QStringList m_games;
|
||||
|
||||
// priority of the plugin
|
||||
int m_priority;
|
||||
|
||||
public:
|
||||
GameFeatureWithData(std::shared_ptr<MOBase::GameFeature> feature,
|
||||
MOBase::IPlugin* plugin, QStringList games, int priority)
|
||||
: m_feature(feature), m_plugin(plugin), m_games(games), m_priority(priority)
|
||||
{}
|
||||
|
||||
const auto& feature() const { return m_feature; }
|
||||
auto* const plugin() const { return m_plugin; }
|
||||
const auto& games() const { return m_games; }
|
||||
auto priority() const { return m_priority; }
|
||||
};
|
||||
|
||||
// retrieve a game feature from info
|
||||
//
|
||||
std::shared_ptr<MOBase::GameFeature> gameFeature(std::type_info const& index) const;
|
||||
|
||||
// update current features by filtering
|
||||
//
|
||||
void updateCurrentFeatures(std::type_index const& index);
|
||||
void updateCurrentFeatures();
|
||||
|
||||
// implementation details
|
||||
class CombinedModDataChecker;
|
||||
class CombinedModDataContent;
|
||||
|
||||
CombinedModDataChecker& modDataChecker() const;
|
||||
CombinedModDataContent& modDataContent() const;
|
||||
|
||||
PluginContainer& m_pluginContainer;
|
||||
|
||||
std::unordered_map<std::type_index, std::vector<GameFeatureWithData>> m_allFeatures;
|
||||
std::unordered_map<std::type_index, std::vector<std::shared_ptr<MOBase::GameFeature>>>
|
||||
m_currentFeatures;
|
||||
|
||||
std::shared_ptr<CombinedModDataChecker> m_modDataChecker;
|
||||
std::shared_ptr<CombinedModDataContent> m_modDataContent;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "gamefeaturesproxy.h"
|
||||
|
||||
#include "game_features.h"
|
||||
#include "organizerproxy.h"
|
||||
|
||||
GameFeaturesProxy::GameFeaturesProxy(OrganizerProxy* coreProxy,
|
||||
GameFeatures& gameFeatures)
|
||||
: m_CoreProxy(*coreProxy), m_Features(gameFeatures)
|
||||
{}
|
||||
|
||||
bool GameFeaturesProxy::registerFeature(QStringList const& games,
|
||||
std::shared_ptr<MOBase::GameFeature> feature,
|
||||
int priority, bool replace)
|
||||
{
|
||||
if (replace) {
|
||||
m_Features.unregisterGameFeatures(m_CoreProxy.plugin(), feature->typeInfo());
|
||||
}
|
||||
return m_Features.registerGameFeature(m_CoreProxy.plugin(), games, feature, priority);
|
||||
}
|
||||
|
||||
bool GameFeaturesProxy::registerFeature(MOBase::IPluginGame* game,
|
||||
std::shared_ptr<MOBase::GameFeature> feature,
|
||||
int priority, bool replace)
|
||||
{
|
||||
return registerFeature({game->gameName()}, feature, priority, replace);
|
||||
}
|
||||
|
||||
bool GameFeaturesProxy::registerFeature(std::shared_ptr<MOBase::GameFeature> feature,
|
||||
int priority, bool replace)
|
||||
{
|
||||
return registerFeature(QStringList(), feature, priority, replace);
|
||||
}
|
||||
|
||||
bool GameFeaturesProxy::unregisterFeature(std::shared_ptr<MOBase::GameFeature> feature)
|
||||
{
|
||||
return m_Features.unregisterGameFeature(feature);
|
||||
}
|
||||
|
||||
std::shared_ptr<MOBase::GameFeature>
|
||||
GameFeaturesProxy::gameFeatureImpl(std::type_info const& info) const
|
||||
{
|
||||
return m_Features.gameFeature(info);
|
||||
}
|
||||
|
||||
int GameFeaturesProxy::unregisterFeaturesImpl(std::type_info const& info)
|
||||
{
|
||||
return m_Features.unregisterGameFeatures(m_CoreProxy.plugin(), info);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user