From f913cacf69c8cb2eb7784e93f9823736194ea200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Fri, 31 May 2024 21:21:52 +0200 Subject: [PATCH] Refactoring of game features for better management. --- src/CMakeLists.txt | 2 + src/activatemodsdialog.cpp | 2 + src/activatemodsdialog.h | 7 +- src/directoryrefresher.cpp | 26 +-- src/directoryrefresher.h | 6 +- src/game_features.cpp | 332 ++++++++++++++++++++++++++++++++ src/game_features.h | 115 +++++++++++ src/gamefeaturesproxy.cpp | 48 +++++ src/gamefeaturesproxy.h | 34 ++++ src/mainwindow.cpp | 20 +- src/modinfo.cpp | 5 +- src/modinforegular.cpp | 3 +- src/modinfowithconflictinfo.cpp | 2 +- src/organizer_en.ts | 320 +++++++++++++++--------------- src/organizercore.cpp | 40 ++-- src/organizercore.h | 8 +- src/organizerproxy.cpp | 10 +- src/organizerproxy.h | 10 +- src/plugincontainer.cpp | 16 +- src/plugincontainer.h | 14 +- src/pluginlist.cpp | 10 +- src/profile.cpp | 28 +-- src/profile.h | 8 +- src/profilesdialog.cpp | 16 +- src/profilesdialog.h | 10 +- src/savestab.cpp | 9 +- src/savestab.h | 2 +- 27 files changed, 841 insertions(+), 262 deletions(-) create mode 100644 src/game_features.cpp create mode 100644 src/game_features.h create mode 100644 src/gamefeaturesproxy.cpp create mode 100644 src/gamefeaturesproxy.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b9a0f852..8faff52ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,7 @@ mo2_add_filter(NAME src/core GROUPS nexusinterface nxmaccessmanager organizercore + game_features plugincontainer apiuseraccount processrunner @@ -196,6 +197,7 @@ mo2_add_filter(NAME src/profiles GROUPS mo2_add_filter(NAME src/proxies GROUPS downloadmanagerproxy + gamefeaturesproxy modlistproxy organizerproxy pluginlistproxy diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index 67ad5b483..a95e78d39 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -28,6 +28,8 @@ along with Mod Organizer. If not, see . #include +using namespace MOBase; + ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets, QWidget* parent) : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog) diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index f7fd719f5..ea9a11483 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -24,9 +24,8 @@ along with Mod Organizer. If not, see . #include "tutorabledialog.h" #include - -class QString; -class QWidget; +#include +#include #include @@ -49,7 +48,7 @@ class ActivateModsDialog : public MOBase::TutorableDialog * @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(); diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 3313b8c5e..e195d3a60 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -22,9 +22,11 @@ along with Mod Organizer. If not, see . #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" @@ -159,8 +161,8 @@ void dumpStats(std::vector& stats) ++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(); - QStringList loadOrder; - GamePlugins* gamePlugins = game->feature(); + auto* gamePlugins = m_Core.gameFeatures().gameFeature(); 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(); - QStringList loadOrder; - GamePlugins* gamePlugins = game->feature(); + GamePlugins* gamePlugins = gameFeatures->gameFeature(); 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) { diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 2e6de1a06..771aaa952 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -30,6 +30,8 @@ along with Mod Organizer. If not, see . #include #include +class OrganizerCore; + /** * @brief used to asynchronously generate the virtual view of the combined data *directory @@ -54,7 +56,7 @@ class DirectoryRefresher : public QObject int priority; }; - DirectoryRefresher(std::size_t threadCount); + DirectoryRefresher(OrganizerCore* core, std::size_t threadCount); /** * @brief retrieve the updated directory structure @@ -146,6 +148,8 @@ public slots: void refreshed(); private: + OrganizerCore& m_Core; + std::vector m_Mods; std::set m_EnabledArchives; std::unique_ptr m_Root; diff --git a/src/game_features.cpp b/src/game_features.cpp new file mode 100644 index 000000000..e073d3a70 --- /dev/null +++ b/src/game_features.cpp @@ -0,0 +1,332 @@ +#include "game_features.h" + +#include + +#include + +#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> m_checkers; + mutable std::shared_ptr m_fixer = nullptr; + +public: + void setCheckers(std::vector> checkers) + { + m_checkers = std::move(checkers); + m_fixer = nullptr; + } + + bool isValid() const { return m_checkers.empty(); } + + CheckReturn + dataLooksValid(std::shared_ptr fileTree) const override + { + m_fixer = nullptr; + + for (auto& modDataChecker : m_checkers) { + auto check = modDataChecker->dataLooksValid(fileTree); + + switch (check) { + case CheckReturn::FIXABLE: + m_fixer = modDataChecker; + [[fallthrough]]; + case CheckReturn::VALID: + return CheckReturn::VALID; + case CheckReturn::INVALID: + break; + } + } + return CheckReturn::INVALID; + } + + std::shared_ptr + fix(std::shared_ptr 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, int>> m_modDataContents; + std::vector m_allContents; + +public: + void setContents(std::vector> 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(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(); + } + } + + bool isValid() const { return !m_modDataContents.empty(); } + + std::vector getAllContents() const override { return m_allContents; } + + std::vector + getContentsFor(std::shared_ptr fileTree) const + { + std::vector 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()), + m_modDataContent(std::make_unique()) +{ + // 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::CombinedModDataChecker& GameFeatures::modDataChecker() const +{ + return dynamic_cast(*m_modDataChecker); +} +GameFeatures::CombinedModDataContent& GameFeatures::modDataContent() const +{ + return dynamic_cast(*m_modDataContent); +} + +void GameFeatures::updateCurrentFeatures(std::type_index const& index) +{ + auto& features = m_allFeatures[index]; + + m_currentFeatures[index].clear(); + 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> checkers; + checkers.reserve(currentCheckers.size()); + std::transform(currentCheckers.begin(), currentCheckers.end(), + std::back_inserter(checkers), [](auto const& checker) { + return std::dynamic_pointer_cast(checker); + }); + modDataChecker().setCheckers(std::move(checkers)); + emit modDataCheckerUpdated(gameFeature()); + } + + // update mod data content + if (index == ModDataContentIndex) { + auto& currentContents = m_currentFeatures[ModDataContentIndex]; + std::vector> contents; + contents.reserve(currentContents.size()); + std::transform(currentContents.begin(), currentContents.end(), + std::back_inserter(contents), [](auto const& checker) { + return std::dynamic_pointer_cast(checker); + }); + modDataContent().setContents(std::move(contents)); + emit modDataContentUpdated(gameFeature()); + } +} + +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 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::iterator it; + if (dynamic_cast(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::min()); + + // TODO: only update if relevant + updateCurrentFeatures(feature->typeInfo()); + + return true; +} + +// unregister game features +// +bool GameFeatures::unregisterGameFeature(std::shared_ptr 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; +} + +GameFeature* GameFeatures::gameFeature(std::type_info const& info) const +{ + if (info == ModDataCheckerIndex) { + return modDataChecker().isValid() ? m_modDataChecker.get() : nullptr; + } + + if (info == ModDataContentIndex) { + return modDataContent().isValid() ? m_modDataContent.get() : nullptr; + } + + auto it = m_currentFeatures.find(info); + if (it == m_currentFeatures.end() || it->second.empty()) { + return nullptr; + } + + return it->second.front().get(); +} diff --git a/src/game_features.h b/src/game_features.h new file mode 100644 index 000000000..8e9a5cb35 --- /dev/null +++ b/src/game_features.h @@ -0,0 +1,115 @@ +#ifndef MO2_GAME_FEATURES_H +#define MO2_GAME_FEATURES_H + +#include +#include +#include +#include +#include + +#include + +#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); + + // register game features + // + bool registerGameFeature(MOBase::IPlugin* plugin, QStringList const& games, + std::shared_ptr feature, int priority); + + // unregister game features + // + bool unregisterGameFeature(std::shared_ptr feature); + int unregisterGameFeatures(MOBase::IPlugin* plugin, std::type_info const& info); + + // retrieve a game feature + // + template + T* gameFeature() const + { + return dynamic_cast(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 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 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 + // + 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> m_allFeatures; + std::unordered_map>> + m_currentFeatures; + + std::unique_ptr m_modDataChecker; + std::unique_ptr m_modDataContent; +}; + +#endif diff --git a/src/gamefeaturesproxy.cpp b/src/gamefeaturesproxy.cpp new file mode 100644 index 000000000..21e38617c --- /dev/null +++ b/src/gamefeaturesproxy.cpp @@ -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 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 feature, + int priority, bool replace) +{ + return registerFeature({game->gameName()}, feature, priority, replace); +} + +bool GameFeaturesProxy::registerFeature(std::shared_ptr feature, + int priority, bool replace) +{ + return registerFeature({}, feature, priority, replace); +} + +bool GameFeaturesProxy::unregisterFeature(std::shared_ptr feature) +{ + return m_Features.unregisterGameFeature(feature); +} + +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); +} diff --git a/src/gamefeaturesproxy.h b/src/gamefeaturesproxy.h new file mode 100644 index 000000000..dd9cee519 --- /dev/null +++ b/src/gamefeaturesproxy.h @@ -0,0 +1,34 @@ +#ifndef GAMEFEATURESPROXY_H +#define GAMEFEATURESPROXY_H + +#include "igamefeatures.h" + + +class GameFeatures; +class OrganizerProxy; + +class GameFeaturesProxy : public MOBase::IGameFeatures +{ +public: + GameFeaturesProxy(OrganizerProxy* coreProxy, GameFeatures& gameFeatures); + + bool registerFeature(QStringList const& games, + std::shared_ptr feature, int priority, + bool replace) override; + bool registerFeature(MOBase::IPluginGame* game, + std::shared_ptr feature, int priority, + bool replace) override; + bool registerFeature(std::shared_ptr feature, int priority, + bool replace) override; + bool unregisterFeature(std::shared_ptr feature) override; + +protected: + MOBase::GameFeature* gameFeatureImpl(std::type_info const& info) const override; + int unregisterFeaturesImpl(std::type_info const& info) override; + +private: + GameFeatures& m_Features; + OrganizerProxy& m_CoreProxy; +}; + +#endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ca738c788..4bbf906bb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -332,11 +332,11 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, m_SavesTab.reset(new SavesTab(this, m_OrganizerCore, ui)); // Hide stuff we do not need: - IPluginGame const* game = m_OrganizerCore.managedGame(); - if (!game->feature()) { + auto& features = m_OrganizerCore.gameFeatures(); + if (!features.gameFeature()) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->espTab)); } - if (!game->feature()) { + if (!features.gameFeature()) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->bsaTab)); } @@ -1796,7 +1796,8 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) activateSelectedProfile(); - LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature(); + LocalSavegames* saveGames = + m_OrganizerCore.gameFeatures().gameFeature(); if (saveGames != nullptr) { if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) { m_SavesTab->refreshSaveList(); @@ -1804,7 +1805,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) } BSAInvalidation* invalidation = - m_OrganizerCore.managedGame()->feature(); + m_OrganizerCore.gameFeatures().gameFeature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { QTimer::singleShot(5, [this] { @@ -1923,7 +1924,7 @@ void MainWindow::updateBSAList(const QStringList& defaultArchives, std::vector> items; BSAInvalidation* invalidation = - m_OrganizerCore.managedGame()->feature(); + m_OrganizerCore.gameFeatures().gameFeature(); std::vector files = m_OrganizerCore.directoryStructure()->getFiles(); QStringList plugins = @@ -2032,7 +2033,7 @@ void MainWindow::updateBSAList(const QStringList& defaultArchives, void MainWindow::checkBSAList() { - DataArchives* archives = m_OrganizerCore.managedGame()->feature(); + DataArchives* archives = m_OrganizerCore.gameFeatures().gameFeature(); if (archives != nullptr) { ui->bsaList->blockSignals(true); @@ -2384,7 +2385,8 @@ void MainWindow::on_actionAdd_Profile_triggered() } } - LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature(); + LocalSavegames* saveGames = + m_OrganizerCore.gameFeatures().gameFeature(); if (saveGames != nullptr) { if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) { m_SavesTab->refreshSaveList(); @@ -2392,7 +2394,7 @@ void MainWindow::on_actionAdd_Profile_triggered() } BSAInvalidation* invalidation = - m_OrganizerCore.managedGame()->feature(); + m_OrganizerCore.gameFeatures().gameFeature(); if (invalidation != nullptr) { if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) { QTimer::singleShot(5, [this] { diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 1028c66f1..d7943d360 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -249,8 +249,9 @@ void ModInfo::updateFromDisc(const QString& modsDirectory, OrganizerCore& core, } } - auto* game = core.managedGame(); - UnmanagedMods* unmanaged = game->feature(); + auto* game = core.managedGame(); + auto& features = core.pluginContainer().gameFeatures(); + auto* unmanaged = features.gameFeature(); if (unmanaged != nullptr) { for (const QString& modName : unmanaged->mods(!displayForeign)) { ModInfo::EModType modType = diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index c79fc5740..43949fb4a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -703,7 +703,8 @@ std::vector ModInfoRegular::getFlags() const std::set ModInfoRegular::doGetContents() const { - ModDataContent* contentFeature = m_Core.managedGame()->feature(); + ModDataContent* contentFeature = + m_Core.pluginContainer().gameFeatures().gameFeature(); if (contentFeature) { auto result = contentFeature->getContentsFor(fileTree()); diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 8d4963917..2300405a4 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -303,7 +303,7 @@ void ModInfoWithConflictInfo::prefetch() bool ModInfoWithConflictInfo::doIsValid() const { - auto mdc = m_Core.managedGame()->feature(); + auto mdc = m_Core.gameFeatures().gameFeature(); if (mdc) { auto qdirfiletree = fileTree(); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 1295a3620..812b03f9d 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -170,7 +170,7 @@ p, li { white-space: pre-wrap; } - + not found @@ -3261,7 +3261,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Sort the plugins using LOOT. @@ -3653,7 +3653,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Endorse Mod Organizer @@ -3950,281 +3950,281 @@ As a final option, you can disable Nexus category mapping altogether, which can - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + Update available - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + None of your %1 mods appear to have had recent file updates. - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Error %1: Request to Nexus failed: %2 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + Remove '%1' from the toolbar - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file @@ -4256,17 +4256,17 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - + The update check has found a mod with a Nexus ID and source game of %1, but this game is not a valid Nexus source. - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. @@ -4693,12 +4693,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -5884,42 +5884,42 @@ Please enter a name: OrganizerCore - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + Failed to write settings - + An error occurred trying to write back MO settings to %1: %2 - + Download started - + Download failed - + The selected profile '%1' does not exist. The profile '%2' will be used instead @@ -6021,70 +6021,70 @@ Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + Error - + The designated write target "%1" is not enabled. @@ -6210,48 +6210,48 @@ Continue? PluginContainer - + Plugin error - + Mod Organizer failed to load the plugin '%1' last time it was started. - + The plugin can be skipped for this session, blacklisted, or loaded normally, in which case it might fail again. Blacklisted plugins can be re-enabled later in the settings. - + Skip this plugin - + Blacklist this plugin - + Load this plugin - + Some plugins could not be loaded - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: @@ -6643,71 +6643,71 @@ p, li { white-space: pre-wrap; } Profile - + invalid profile name: %1 - + failed to create %1 - + failed to write mod list: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 - + failed to create tweaked ini: %1 - + failed to open %1 - + "%1" is missing or inaccessible - - - - - + + + + + invalid mod index: %1 - + A mod named "overwrite" was detected, disabled, and moved to the highest priority on the mod list. You may want to rename this mod and enable it again. - + Delete profile-specific save games? - + Do you want to delete the profile-specific save games? (If you select "No", the save games will show up again if you re-enable profile-specific save games) - + Missing profile-specific game INI files! - + Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want to double-check your settings. Missing files: @@ -6715,12 +6715,12 @@ Missing files: - + Delete profile-specific game INI files? - + Do you want to delete the profile-specific game INI files? (If you select "No", the INI files will be used again if you re-enable profile-specific game INI files.) @@ -6890,103 +6890,103 @@ p, li { white-space: pre-wrap; } - + Archive invalidation isn't required for this game. - + This game does not support profile-specific game saves. - - + + failed to create profile: %1 - + Name - + Please enter a name for the new profile - + failed to copy profile: %1 - + Invalid name - + Invalid profile name - + Deleting active profile - + Unable to delete active profile. Please change to a different profile first. - + Confirm - + Are you sure you want to remove this profile (including profile-specific save games, if any)? - + Profile broken - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + Renaming active profile - + The active profile cannot be renamed. Please change to a different profile first. - + Rename Profile - + New Name - + failed to change archive invalidation state: %1 - + failed to determine if invalidation is active: %1 @@ -7481,7 +7481,7 @@ Destination: - + @@ -7493,7 +7493,7 @@ Destination: - + Failed to create "%1". Your user account probably lacks permission. @@ -7610,13 +7610,13 @@ Destination: - - + + <Manage...> - + failed to parse profile %1: %2 @@ -7754,12 +7754,12 @@ Destination: - + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. - + failed to initialize plugin %1: %2 @@ -7791,12 +7791,12 @@ Destination: - + failed to create %1 - + Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! @@ -8297,27 +8297,27 @@ You can restart Mod Organizer as administrator and try launching the program aga - + %1, #%2, Level %3, %4 - + failed to open %1 - + wrong file format - expected %1 got '%2' for %3 - + failed to query registry path (preflight): %1 - + failed to query registry path (read): %1 @@ -8601,17 +8601,17 @@ p, li { white-space: pre-wrap; } SavesTab - + %1 more - + Confirm - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -8619,12 +8619,12 @@ p, li { white-space: pre-wrap; } - + Fix enabled mods... - + Delete %n save(s) @@ -8632,7 +8632,7 @@ p, li { white-space: pre-wrap; } - + Open in Explorer... diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2f45116ce..163738d71 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -92,7 +92,7 @@ OrganizerCore::OrganizerCore(Settings& settings) : m_UserInterface(nullptr), m_PluginContainer(nullptr), m_CurrentProfile(nullptr), m_Settings(settings), m_Updater(&NexusInterface::instance()), m_ModList(m_PluginContainer, this), m_PluginList(*this), - m_DirectoryRefresher(new DirectoryRefresher(settings.refreshThreadCount())), + m_DirectoryRefresher(new DirectoryRefresher(this, settings.refreshThreadCount())), m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)), m_VirtualFileTree([this]() { return VirtualFileTree::makeTree(m_DirectoryStructure); @@ -135,16 +135,6 @@ OrganizerCore::OrganizerCore(Settings& settings) connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const*)), &m_PluginList, SLOT(managedGameChanged(MOBase::IPluginGame const*))); - connect(this, &OrganizerCore::managedGameChanged, - [this](IPluginGame const* gamePlugin) { - ModDataContent* contentFeature = gamePlugin->feature(); - if (contentFeature) { - m_Contents = ModDataContentHolder(contentFeature->getAllContents()); - } else { - m_Contents = ModDataContentHolder(); - } - }); - connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, &DelayedFileWriterBase::write); @@ -273,6 +263,15 @@ void OrganizerCore::connectPlugins(PluginContainer* container) connect(m_PluginContainer, &PluginContainer::pluginDisabled, [&](IPlugin* plugin) { m_PluginDisabled(plugin); }); + + connect(&m_PluginContainer->gameFeatures(), &GameFeatures::modDataContentUpdated, + [this](ModDataContent const* contentFeature) { + if (contentFeature) { + m_Contents = ModDataContentHolder(contentFeature->getAllContents()); + } else { + m_Contents = ModDataContentHolder(); + } + }); } void OrganizerCore::setManagedGame(MOBase::IPluginGame* game) @@ -454,7 +453,7 @@ void OrganizerCore::createDefaultProfile() QString profilesPath = settings().paths().profiles(); if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { Profile newProf(QString::fromStdWString(AppConfig::defaultProfileName()), - managedGame(), false); + managedGame(), gameFeatures(), false); m_ProfileCreated(&newProf); } @@ -569,7 +568,8 @@ void OrganizerCore::setCurrentProfile(const QString& profileName) // Keep the old profile to emit signal-changed: auto oldProfile = std::move(m_CurrentProfile); - m_CurrentProfile = std::make_unique(QDir(profileDir), managedGame()); + m_CurrentProfile = + std::make_unique(QDir(profileDir), managedGame(), gameFeatures()); m_ModList.setProfile(m_CurrentProfile.get()); @@ -1249,7 +1249,7 @@ void OrganizerCore::refreshBSAList() { TimeThis tt("OrganizerCore::refreshBSAList()"); - DataArchives* archives = m_GamePlugin->feature(); + auto* archives = gameFeatures().gameFeature(); if (archives != nullptr) { m_ArchivesInit = false; @@ -1482,6 +1482,11 @@ PluginContainer& OrganizerCore::pluginContainer() const return *m_PluginContainer; } +GameFeatures& OrganizerCore::gameFeatures() const +{ + return pluginContainer().gameFeatures(); +} + IPluginGame const* OrganizerCore::managedGame() const { return m_GamePlugin; @@ -1806,7 +1811,7 @@ void OrganizerCore::syncOverwrite() QString OrganizerCore::oldMO1HookDll() const { - if (auto extender = managedGame()->feature()) { + if (auto extender = gameFeatures().gameFeature()) { QString hookdll = QDir::toNativeSeparators(managedGame()->dataDirectory().absoluteFilePath( extender->PluginPath() + "/hook.dll")); @@ -1999,7 +2004,8 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, } IPluginGame* game = qApp->property("managed_game").value(); - Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game); + Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game, + gameFeatures()); MappingType result; @@ -2038,7 +2044,7 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, } if (m_CurrentProfile->localSavesEnabled()) { - LocalSavegames* localSaves = game->feature(); + LocalSavegames* localSaves = gameFeatures().gameFeature(); if (localSaves != nullptr) { MappingType saveMap = localSaves->mappings(currentProfile()->absolutePath() + "/saves"); diff --git a/src/organizercore.h b/src/organizercore.h index 5bf6f4b64..726babfdf 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -39,6 +39,7 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; class IUserInterface; +class GameFeatures; class PluginContainer; class DirectoryRefresher; @@ -106,7 +107,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose struct ModDataContentHolder { - using Content = ModDataContent::Content; + using Content = MOBase::ModDataContent::Content; /** * @return true if the hold list of contents is empty, false otherwise. @@ -205,7 +206,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose /** * @brief Construct a ModDataContentHold holding the given list of contents. */ - ModDataContentHolder(std::vector contents) + ModDataContentHolder(std::vector contents) : m_Contents(std::move(contents)) {} @@ -275,6 +276,9 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose // PluginContainer& pluginContainer() const; + // return the game features + GameFeatures& gameFeatures() const; + MOBase::IPluginGame const* managedGame() const; /** diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 0c1fb4099..77d973949 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -1,6 +1,7 @@ #include "organizerproxy.h" #include "downloadmanagerproxy.h" +#include "gamefeaturesproxy.h" #include "glob_matching.h" #include "modlistproxy.h" #include "organizercore.h" @@ -25,7 +26,9 @@ OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, std::make_unique(this, organizer->downloadManager())), m_ModListProxy(std::make_unique(this, organizer->modList())), m_PluginListProxy( - std::make_unique(this, organizer->pluginList())) + std::make_unique(this, organizer->pluginList())), + m_GameFeaturesProxy(std::make_unique( + this, organizer->pluginContainer().gameFeatures())) {} OrganizerProxy::~OrganizerProxy() @@ -310,6 +313,11 @@ MOBase::IModList* OrganizerProxy::modList() const return m_ModListProxy.get(); } +MOBase::IGameFeatures* OrganizerProxy::gameFeatures() const +{ + return m_GameFeaturesProxy.get(); +} + MOBase::IProfile* OrganizerProxy::profile() const { return m_Proxied->currentProfile(); diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 751464328..fbae7cde6 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -8,6 +8,7 @@ #include "organizercore.h" +class GameFeaturesProxy; class PluginContainer; class DownloadManagerProxy; class ModListProxy; @@ -59,10 +60,12 @@ class OrganizerProxy : public MOBase::IOrganizer const std::function& filter) const override; virtual std::shared_ptr virtualFileTree() const override; - virtual MOBase::IDownloadManager* downloadManager() const; - virtual MOBase::IPluginList* pluginList() const; - virtual MOBase::IModList* modList() const; + virtual MOBase::IDownloadManager* downloadManager() const override; + virtual MOBase::IPluginList* pluginList() const override; + virtual MOBase::IModList* modList() const override; virtual MOBase::IProfile* profile() const override; + virtual MOBase::IGameFeatures* gameFeatures() const override; + virtual HANDLE startApplication(const QString& executable, const QStringList& args = QStringList(), const QString& cwd = "", const QString& profile = "", @@ -151,6 +154,7 @@ class OrganizerProxy : public MOBase::IOrganizer std::unique_ptr m_DownloadManagerProxy; std::unique_ptr m_ModListProxy; std::unique_ptr m_PluginListProxy; + std::unique_ptr m_GameFeaturesProxy; }; #endif // ORGANIZERPROXY_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 157d33995..3b0525f25 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -320,7 +320,9 @@ void PluginRequirements::requiredFor(std::vector& required, // PluginContainer PluginContainer::PluginContainer(OrganizerCore* organizer) - : m_Organizer(organizer), m_UserInterface(nullptr), m_PreviewGenerator(*this) + : m_Organizer(organizer), m_UserInterface(nullptr), + m_GameFeatures(std::make_unique(organizer, this)), + m_PreviewGenerator(*this) {} PluginContainer::~PluginContainer() @@ -625,7 +627,7 @@ IPlugin* PluginContainer::registerPlugin(QObject* plugin, const QString& filepat return nullptr; } -IPlugin* PluginContainer::managedGame() const +IPluginGame* PluginContainer::managedGame() const { // TODO: This const_cast is safe but ugly. Most methods require a IPlugin*, so // returning a const-version if painful. This should be fixed by making methods accept @@ -663,9 +665,8 @@ void PluginContainer::setEnabled(MOBase::IPlugin* plugin, bool enable, // If required, disable dependencies: if (!enable && dependencies) { for (auto* p : requirements(plugin).requiredFor()) { - setEnabled( - p, false, - false); // No need to "recurse" here since requiredFor already does it. + // No need to "recurse" here since requiredFor already does it. + setEnabled(p, false, false); } } @@ -760,11 +761,6 @@ IPluginGame* PluginContainer::game(const QString& name) const } } -const PreviewGenerator& PluginContainer::previewGenerator() const -{ - return m_PreviewGenerator; -} - void PluginContainer::startPluginsImpl(const std::vector& plugins) const { // setUserInterface() diff --git a/src/plugincontainer.h b/src/plugincontainer.h index d5ceca723..4854954d3 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -24,6 +24,8 @@ class IUserInterface; #include #include +#include "game_features.h" + class OrganizerProxy; /** @@ -279,7 +281,7 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose /** * @return the IPlugin interface to the currently managed game. */ - MOBase::IPlugin* managedGame() const; + MOBase::IPluginGame* managedGame() const; /** * @brief Check if the given plugin is enabled. @@ -337,10 +339,15 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose */ QString topImplementedInterface(MOBase::IPlugin* plugin) const; + /** + * @return the game features. + */ + GameFeatures& gameFeatures() const { return *m_GameFeatures; } + /** * @return the preview generator. */ - const PreviewGenerator& previewGenerator() const; + const PreviewGenerator& previewGenerator() const { return m_PreviewGenerator; } /** * @return the list of plugin file names, including proxied plugins. @@ -476,6 +483,9 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose // Main user interface, can be null until MW has been initialized. IUserInterface* m_UserInterface; + // Game features + std::unique_ptr m_GameFeatures; + PluginMap m_Plugins; // This maps allow access to IPlugin* from name or diagnose/mapper object. diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index b1fd97579..8953d0e15 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -177,7 +177,7 @@ void PluginList::refresh(const QString& profileName, QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); QStringList enabledPlugins = m_GamePlugin->enabledPlugins(); - GamePlugins* gamePlugins = m_GamePlugin->feature(); + GamePlugins* gamePlugins = m_Organizer.gameFeatures().gameFeature(); const bool lightPluginsAreSupported = gamePlugins ? gamePlugins->lightPluginsAreSupported() : false; const bool overridePluginsAreSupported = @@ -673,7 +673,7 @@ void PluginList::writeLockedOrder(const QString& fileName) const void PluginList::saveTo(const QString& lockedOrderFileName) const { - GamePlugins* gamePlugins = m_GamePlugin->feature(); + GamePlugins* gamePlugins = m_Organizer.gameFeatures().gameFeature(); if (gamePlugins) { gamePlugins->writePluginLists(m_Organizer.managedGameOrganizer()->pluginList()); } @@ -1085,7 +1085,7 @@ void PluginList::generatePluginIndexes() int numESLs = 0; int numSkipped = 0; - GamePlugins* gamePlugins = m_GamePlugin->feature(); + GamePlugins* gamePlugins = m_Organizer.gameFeatures().gameFeature(); const bool lightPluginsSupported = gamePlugins ? gamePlugins->lightPluginsAreSupported() : false; const bool overridePluginsSupported = @@ -1358,8 +1358,8 @@ QVariant PluginList::tooltipData(const QModelIndex& modelIndex) const } if (esp.forceDisabled) { - if (m_GamePlugin->feature() && esp.hasLightExtension && - !m_GamePlugin->feature()->lightPluginsAreSupported()) { + auto* feature = m_Organizer.gameFeatures().gameFeature(); + if (feature && esp.hasLightExtension && feature->lightPluginsAreSupported()) { toolTip += "

" + tr("Light plugins (ESL) are not supported by this game."); } else { toolTip += "

" + tr("This game does not currently permit custom plugin " diff --git a/src/profile.cpp b/src/profile.cpp index d4299a7b0..0c05e6832 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "profile.h" #include "filesystemutilities.h" +#include "game_features.h" #include "modinfo.h" #include "modinfoforeign.h" #include "registry.h" @@ -73,9 +74,9 @@ void Profile::touchFile(QString fileName) } Profile::Profile(const QString& name, IPluginGame const* gamePlugin, - bool useDefaultSettings) + GameFeatures const& gameFeatures, bool useDefaultSettings) : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)), - m_GamePlugin(gamePlugin) + m_GamePlugin(gamePlugin), m_GameFeatures(gameFeatures) { QString profilesDir = Settings::instance().paths().profiles(); QDir profileBase(profilesDir); @@ -114,8 +115,9 @@ Profile::Profile(const QString& name, IPluginGame const* gamePlugin, refreshModStatus(); } -Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin) - : m_Directory(directory), m_GamePlugin(gamePlugin), +Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin, + GameFeatures const& gameFeatures) + : m_Directory(directory), m_GamePlugin(gamePlugin), m_GameFeatures(gameFeatures), m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) { assert(gamePlugin != nullptr); @@ -138,7 +140,7 @@ Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin) Profile::Profile(const Profile& reference) : m_Directory(reference.m_Directory), m_ModListWriter(std::bind(&Profile::doWriteModlist, this)), - m_GamePlugin(reference.m_GamePlugin) + m_GamePlugin(reference.m_GamePlugin), m_GameFeatures(reference.m_GameFeatures) { m_Settings = @@ -191,9 +193,9 @@ void Profile::findProfileSettings() } if (setting("", "AutomaticArchiveInvalidation") == QVariant()) { - BSAInvalidation* invalidation = m_GamePlugin->feature(); - DataArchives* dataArchives = m_GamePlugin->feature(); - bool found = false; + auto* invalidation = m_GameFeatures.gameFeature(); + DataArchives* dataArchives = m_GameFeatures.gameFeature(); + bool found = false; if ((invalidation != nullptr) && (dataArchives != nullptr)) { for (const QString& archive : dataArchives->archives(this)) { if (invalidation->isInvalidationBSA(archive)) { @@ -731,7 +733,7 @@ Profile* Profile::createPtrFrom(const QString& name, const Profile& reference, { QString profileDirectory = Settings::instance().paths().profiles() + "/" + name; reference.copyFilesTo(profileDirectory); - return new Profile(QDir(profileDirectory), gamePlugin); + return new Profile(QDir(profileDirectory), gamePlugin, reference.m_GameFeatures); } void Profile::copyFilesTo(QString& target) const @@ -811,8 +813,8 @@ void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString& tweakedIni) const bool Profile::invalidationActive(bool* supported) const { - BSAInvalidation* invalidation = m_GamePlugin->feature(); - DataArchives* dataArchives = m_GamePlugin->feature(); + auto* invalidation = m_GameFeatures.gameFeature(); + auto* dataArchives = m_GameFeatures.gameFeature(); if (supported != nullptr) { *supported = ((invalidation != nullptr) && (dataArchives != nullptr)); @@ -825,7 +827,7 @@ bool Profile::invalidationActive(bool* supported) const void Profile::deactivateInvalidation() { - BSAInvalidation* invalidation = m_GamePlugin->feature(); + auto* invalidation = m_GameFeatures.gameFeature(); if (invalidation != nullptr) { invalidation->deactivate(this); @@ -836,7 +838,7 @@ void Profile::deactivateInvalidation() void Profile::activateInvalidation() { - BSAInvalidation* invalidation = m_GamePlugin->feature(); + auto* invalidation = m_GameFeatures.gameFeature(); if (invalidation != nullptr) { invalidation->activate(this); diff --git a/src/profile.h b/src/profile.h index 0adb4d0fe..fcce75ee7 100644 --- a/src/profile.h +++ b/src/profile.h @@ -43,6 +43,8 @@ namespace MOBase class IPluginGame; } +class GameFeatures; + /** * @brief represents a profile **/ @@ -70,7 +72,7 @@ class Profile : public QObject, public MOBase::IProfile * @param filter save game filter. Defaults to <no filter>. **/ Profile(const QString& name, MOBase::IPluginGame const* gamePlugin, - bool useDefaultSettings); + GameFeatures const& features, bool useDefaultSettings); /** * @brief constructor @@ -80,7 +82,8 @@ class Profile : public QObject, public MOBase::IProfile *so technically, invoking this should always produce a working profile * @param directory directory to read the profile from **/ - Profile(const QDir& directory, MOBase::IPluginGame const* gamePlugin); + Profile(const QDir& directory, MOBase::IPluginGame const* gamePlugin, + GameFeatures const& features); Profile(const Profile& reference); @@ -398,6 +401,7 @@ protected slots: QSettings* m_Settings; + const GameFeatures& m_GameFeatures; const MOBase::IPluginGame* m_GamePlugin; std::vector m_ModStatus; diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index f8010adaa..72c1ae721 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "bsainvalidation.h" #include "filesystemutilities.h" +#include "game_features.h" #include "iplugingame.h" #include "localsavegames.h" #include "organizercore.h" @@ -52,7 +53,8 @@ Q_DECLARE_METATYPE(Profile::Ptr) ProfilesDialog::ProfilesDialog(const QString& profileName, OrganizerCore& organizer, QWidget* parent) : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), - m_FailState(false), m_Game(organizer.managedGame()), m_ActiveProfileName("") + m_GameFeatures(organizer.gameFeatures()), m_FailState(false), + m_Game(organizer.managedGame()), m_ActiveProfileName("") { ui->setupUi(this); @@ -70,15 +72,14 @@ ProfilesDialog::ProfilesDialog(const QString& profileName, OrganizerCore& organi } } - BSAInvalidation* invalidation = m_Game->feature(); - + auto* invalidation = m_GameFeatures.gameFeature(); if (invalidation == nullptr) { ui->invalidationBox->setToolTip( tr("Archive invalidation isn't required for this game.")); ui->invalidationBox->setEnabled(false); } - if (!m_Game->feature()) { + if (!m_GameFeatures.gameFeature()) { ui->localSavesBox->setToolTip( tr("This game does not support profile-specific game saves.")); ui->localSavesBox->setEnabled(false); @@ -149,8 +150,8 @@ QListWidgetItem* ProfilesDialog::addItem(const QString& name) QListWidgetItem* newItem = new QListWidgetItem(profileDir.dirName(), ui->profilesList); try { - newItem->setData(Qt::UserRole, QVariant::fromValue( - Profile::Ptr(new Profile(profileDir, m_Game)))); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile( + profileDir, m_Game, m_GameFeatures)))); m_FailState = false; } catch (const std::exception& e) { reportError(tr("failed to create profile: %1").arg(e.what())); @@ -161,7 +162,8 @@ QListWidgetItem* ProfilesDialog::addItem(const QString& name) void ProfilesDialog::createProfile(const QString& name, bool useDefaultSettings) { try { - auto profile = Profile::Ptr(new Profile(name, m_Game, useDefaultSettings)); + auto profile = + Profile::Ptr(new Profile(name, m_Game, m_GameFeatures, useDefaultSettings)); QListWidgetItem* newItem = new QListWidgetItem(name, ui->profilesList); newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); ui->profilesList->addItem(newItem); diff --git a/src/profilesdialog.h b/src/profilesdialog.h index dc8329a26..1a2a36e24 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -20,13 +20,12 @@ along with Mod Organizer. If not, see . #ifndef PROFILESDIALOG_H #define PROFILESDIALOG_H +#include + #include "tutorabledialog.h" -class Profile; -class OrganizerCore; class QListWidget; class QListWidgetItem; -#include class QString; namespace Ui @@ -39,6 +38,10 @@ namespace MOBase class IPluginGame; } +class GameFeatures; +class Profile; +class OrganizerCore; + /** * @brief Dialog that can be used to create/delete/modify profiles **/ @@ -126,6 +129,7 @@ private slots: private: Ui::ProfilesDialog* ui; + GameFeatures& m_GameFeatures; QListWidget* m_ProfilesList; bool m_FailState; MOBase::IPluginGame const* m_Game; diff --git a/src/savestab.cpp b/src/savestab.cpp index 813fea052..540afc0c6 100644 --- a/src/savestab.cpp +++ b/src/savestab.cpp @@ -62,8 +62,7 @@ void SavesTab::displaySaveGameInfo(QTreeWidgetItem* newItem) } if (m_CurrentSaveView == nullptr) { - const IPluginGame* game = m_core.managedGame(); - const SaveGameInfo* info = game->feature(); + const SaveGameInfo* info = m_core.gameFeatures().gameFeature(); if (info != nullptr) { m_CurrentSaveView = info->getSaveGameWidget(m_window); @@ -199,7 +198,7 @@ void SavesTab::refreshSaveList() void SavesTab::deleteSavegame() { - SaveGameInfo const* info = m_core.managedGame()->feature(); + SaveGameInfo const* info = m_core.gameFeatures().gameFeature(); QString savesMsgLabel; QStringList deleteFiles; @@ -246,7 +245,7 @@ void SavesTab::onContextMenu(const QPoint& pos) QMenu menu; - SaveGameInfo const* info = this->m_core.managedGame()->feature(); + SaveGameInfo const* info = m_core.gameFeatures().gameFeature(); if (info != nullptr) { QAction* action = menu.addAction(tr("Fix enabled mods...")); action->setEnabled(false); @@ -304,7 +303,7 @@ void SavesTab::fixMods(SaveGameInfo::MissingAssets const& missingAssets) void SavesTab::openInExplorer() { - const SaveGameInfo* info = m_core.managedGame()->feature(); + const SaveGameInfo* info = m_core.gameFeatures().gameFeature(); const auto sel = ui.list->selectionModel()->selectedRows(); if (sel.empty()) { diff --git a/src/savestab.h b/src/savestab.h index a903e4fb5..6a32daf9f 100644 --- a/src/savestab.h +++ b/src/savestab.h @@ -58,7 +58,7 @@ class SavesTab : public QObject void onContextMenu(const QPoint& pos); void deleteSavegame(); void saveSelectionChanged(QTreeWidgetItem* newItem); - void fixMods(SaveGameInfo::MissingAssets const& missingAssets); + void fixMods(MOBase::SaveGameInfo::MissingAssets const& missingAssets); void refreshSavesIfOpen(); void openInExplorer(); };