summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikaƫl Capelle <capelle.mikael@gmail.com>2024-06-09 12:18:17 +0200
committerGitHub <noreply@github.com>2024-06-09 12:18:17 +0200
commit88c386d74d7d94ec16d4d84ed674eb51e31647ac (patch)
treeadf0b2537f12d11d8df77f68a84926d15b063554
parentc43535f5bcf1043c3b9c00c77967267cf1acf60f (diff)
Refactoring of game features for better management. (#2043)
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/activatemodsdialog.cpp2
-rw-r--r--src/activatemodsdialog.h7
-rw-r--r--src/directoryrefresher.cpp26
-rw-r--r--src/directoryrefresher.h6
-rw-r--r--src/game_features.cpp340
-rw-r--r--src/game_features.h117
-rw-r--r--src/gamefeaturesproxy.cpp48
-rw-r--r--src/gamefeaturesproxy.h34
-rw-r--r--src/mainwindow.cpp21
-rw-r--r--src/modinfo.cpp5
-rw-r--r--src/modinforegular.cpp3
-rw-r--r--src/modinfowithconflictinfo.cpp2
-rw-r--r--src/organizer_en.ts346
-rw-r--r--src/organizercore.cpp47
-rw-r--r--src/organizercore.h8
-rw-r--r--src/organizerproxy.cpp10
-rw-r--r--src/organizerproxy.h10
-rw-r--r--src/plugincontainer.cpp18
-rw-r--r--src/plugincontainer.h14
-rw-r--r--src/pluginlist.cpp10
-rw-r--r--src/profile.cpp28
-rw-r--r--src/profile.h8
-rw-r--r--src/profilesdialog.cpp16
-rw-r--r--src/profilesdialog.h10
-rw-r--r--src/savestab.cpp9
-rw-r--r--src/savestab.h2
27 files changed, 867 insertions, 282 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1b9a0f85..8faff52c 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 67ad5b48..a95e78d3 100644
--- a/src/activatemodsdialog.cpp
+++ b/src/activatemodsdialog.cpp
@@ -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)
diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h
index f7fd719f..ea9a1148 100644
--- a/src/activatemodsdialog.h
+++ b/src/activatemodsdialog.h
@@ -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();
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp
index 3313b8c5..e2e05b2c 100644
--- a/src/directoryrefresher.cpp
+++ b/src/directoryrefresher.cpp
@@ -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"
@@ -159,8 +161,8 @@ void dumpStats(std::vector<DirectoryStats>& 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<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) {
diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h
index 2e6de1a0..771aaa95 100644
--- a/src/directoryrefresher.h
+++ b/src/directoryrefresher.h
@@ -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;
diff --git a/src/game_features.cpp b/src/game_features.cpp
new file mode 100644
index 00000000..bc083bc8
--- /dev/null
+++ b/src/game_features.cpp
@@ -0,0 +1,340 @@
+#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;
+
+ for (auto& modDataChecker : m_modDataCheckers) {
+ 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<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();
+}
diff --git a/src/game_features.h b/src/game_features.h
new file mode 100644
index 00000000..55f35e37
--- /dev/null
+++ b/src/game_features.h
@@ -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
diff --git a/src/gamefeaturesproxy.cpp b/src/gamefeaturesproxy.cpp
new file mode 100644
index 00000000..3adac353
--- /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<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({}, 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);
+}
diff --git a/src/gamefeaturesproxy.h b/src/gamefeaturesproxy.h
new file mode 100644
index 00000000..9ffd6b04
--- /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<MOBase::GameFeature> feature, int priority,
+ bool replace) override;
+ bool registerFeature(MOBase::IPluginGame* game,
+ std::shared_ptr<MOBase::GameFeature> feature, int priority,
+ bool replace) override;
+ bool registerFeature(std::shared_ptr<MOBase::GameFeature> feature, int priority,
+ bool replace) override;
+ bool unregisterFeature(std::shared_ptr<MOBase::GameFeature> feature) override;
+
+protected:
+ std::shared_ptr<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 ca738c78..b14a14a1 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<GamePlugins>()) {
+ auto& features = m_OrganizerCore.gameFeatures();
+ if (!features.gameFeature<GamePlugins>()) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->espTab));
}
- if (!game->feature<DataArchives>()) {
+ if (!features.gameFeature<DataArchives>()) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->bsaTab));
}
@@ -1796,15 +1796,14 @@ void MainWindow::on_profileBox_currentIndexChanged(int index)
activateSelectedProfile();
- LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature<LocalSavegames>();
+ auto saveGames = m_OrganizerCore.gameFeatures().gameFeature<LocalSavegames>();
if (saveGames != nullptr) {
if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) {
m_SavesTab->refreshSaveList();
}
}
- BSAInvalidation* invalidation =
- m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
+ auto invalidation = m_OrganizerCore.gameFeatures().gameFeature<BSAInvalidation>();
if (invalidation != nullptr) {
if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) {
QTimer::singleShot(5, [this] {
@@ -1922,8 +1921,7 @@ void MainWindow::updateBSAList(const QStringList& defaultArchives,
ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
std::vector<std::pair<UINT32, QTreeWidgetItem*>> items;
- BSAInvalidation* invalidation =
- m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
+ auto invalidation = m_OrganizerCore.gameFeatures().gameFeature<BSAInvalidation>();
std::vector<FileEntryPtr> files = m_OrganizerCore.directoryStructure()->getFiles();
QStringList plugins =
@@ -2032,7 +2030,7 @@ void MainWindow::updateBSAList(const QStringList& defaultArchives,
void MainWindow::checkBSAList()
{
- DataArchives* archives = m_OrganizerCore.managedGame()->feature<DataArchives>();
+ auto archives = m_OrganizerCore.gameFeatures().gameFeature<DataArchives>();
if (archives != nullptr) {
ui->bsaList->blockSignals(true);
@@ -2384,15 +2382,14 @@ void MainWindow::on_actionAdd_Profile_triggered()
}
}
- LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature<LocalSavegames>();
+ auto saveGames = m_OrganizerCore.gameFeatures().gameFeature<LocalSavegames>();
if (saveGames != nullptr) {
if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) {
m_SavesTab->refreshSaveList();
}
}
- BSAInvalidation* invalidation =
- m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
+ auto invalidation = m_OrganizerCore.gameFeatures().gameFeature<BSAInvalidation>();
if (invalidation != nullptr) {
if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) {
QTimer::singleShot(5, [this] {
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index 1028c66f..abfd9de6 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<UnmanagedMods>();
+ auto* game = core.managedGame();
+ auto& features = core.pluginContainer().gameFeatures();
+ auto unmanaged = features.gameFeature<UnmanagedMods>();
if (unmanaged != nullptr) {
for (const QString& modName : unmanaged->mods(!displayForeign)) {
ModInfo::EModType modType =
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index c79fc574..d3ff6547 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -703,7 +703,8 @@ std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
std::set<int> ModInfoRegular::doGetContents() const
{
- ModDataContent* contentFeature = m_Core.managedGame()->feature<ModDataContent>();
+ auto contentFeature =
+ m_Core.pluginContainer().gameFeatures().gameFeature<ModDataContent>();
if (contentFeature) {
auto result = contentFeature->getContentsFor(fileTree());
diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp
index 8d496391..2300405a 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<ModDataChecker>();
+ auto mdc = m_Core.gameFeatures().gameFeature<ModDataChecker>();
if (mdc) {
auto qdirfiletree = fileTree();
diff --git a/src/organizer_en.ts b/src/organizer_en.ts
index 1295a362..362ccd89 100644
--- a/src/organizer_en.ts
+++ b/src/organizer_en.ts
@@ -170,7 +170,7 @@ p, li { white-space: pre-wrap; }
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="activatemodsdialog.cpp" line="50"/>
+ <location filename="activatemodsdialog.cpp" line="52"/>
<source>not found</source>
<translation type="unfinished"></translation>
</message>
@@ -3261,7 +3261,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi
<message>
<location filename="mainwindow.ui" line="812"/>
<location filename="mainwindow.ui" line="815"/>
- <location filename="mainwindow.cpp" line="3053"/>
+ <location filename="mainwindow.cpp" line="3050"/>
<source>Sort the plugins using LOOT.</source>
<translation type="unfinished"></translation>
</message>
@@ -3653,7 +3653,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi
<message>
<location filename="mainwindow.ui" line="1832"/>
<location filename="mainwindow.ui" line="1835"/>
- <location filename="mainwindow.cpp" line="2973"/>
+ <location filename="mainwindow.cpp" line="2970"/>
<source>Endorse Mod Organizer</source>
<translation type="unfinished"></translation>
</message>
@@ -3950,281 +3950,281 @@ As a final option, you can disable Nexus category mapping altogether, which can
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="1883"/>
+ <location filename="mainwindow.cpp" line="1882"/>
<source>&lt;Edit...&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="1894"/>
+ <location filename="mainwindow.cpp" line="1893"/>
<source>(no executables)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2060"/>
+ <location filename="mainwindow.cpp" line="2058"/>
<source>This bsa is enabled in the ini file so it may be required!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2111"/>
+ <location filename="mainwindow.cpp" line="2109"/>
<source>Activating Network Proxy</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2207"/>
+ <location filename="mainwindow.cpp" line="2205"/>
<source>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.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2489"/>
+ <location filename="mainwindow.cpp" line="2486"/>
<source>failed to change origin name: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2519"/>
+ <location filename="mainwindow.cpp" line="2516"/>
<source>failed to move &quot;%1&quot; from mod &quot;%2&quot; to &quot;%3&quot;: %4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2646"/>
+ <location filename="mainwindow.cpp" line="2643"/>
<source>Open Game folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2647"/>
+ <location filename="mainwindow.cpp" line="2644"/>
<source>Open MyGames folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2648"/>
+ <location filename="mainwindow.cpp" line="2645"/>
<source>Open INIs folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2653"/>
+ <location filename="mainwindow.cpp" line="2650"/>
<source>Open Instance folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2654"/>
+ <location filename="mainwindow.cpp" line="2651"/>
<source>Open Mods folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2655"/>
+ <location filename="mainwindow.cpp" line="2652"/>
<source>Open Profile folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2656"/>
+ <location filename="mainwindow.cpp" line="2653"/>
<source>Open Downloads folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2662"/>
+ <location filename="mainwindow.cpp" line="2659"/>
<source>Open MO2 Install folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2663"/>
+ <location filename="mainwindow.cpp" line="2660"/>
<source>Open MO2 Plugins folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2664"/>
+ <location filename="mainwindow.cpp" line="2661"/>
<source>Open MO2 Stylesheets folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2666"/>
+ <location filename="mainwindow.cpp" line="2663"/>
<source>Open MO2 Logs folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2746"/>
+ <location filename="mainwindow.cpp" line="2743"/>
<source>Restart Mod Organizer</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2748"/>
+ <location filename="mainwindow.cpp" line="2745"/>
<source>Mod Organizer must restart to finish configuration changes</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2750"/>
+ <location filename="mainwindow.cpp" line="2747"/>
<source>Restart</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2752"/>
+ <location filename="mainwindow.cpp" line="2749"/>
<source>Continue</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2752"/>
+ <location filename="mainwindow.cpp" line="2749"/>
<source>Some things might be weird.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2781"/>
+ <location filename="mainwindow.cpp" line="2778"/>
<source>Can&apos;t change download directory while downloads are in progress!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2936"/>
+ <location filename="mainwindow.cpp" line="2933"/>
<source>Update available</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2974"/>
+ <location filename="mainwindow.cpp" line="2971"/>
<source>Do you want to endorse Mod Organizer on %1 now?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2993"/>
+ <location filename="mainwindow.cpp" line="2990"/>
<source>Abstain from Endorsing Mod Organizer</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="2994"/>
+ <location filename="mainwindow.cpp" line="2991"/>
<source>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.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3022"/>
+ <location filename="mainwindow.cpp" line="3019"/>
<source>Thank you for endorsing MO2! :)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3027"/>
+ <location filename="mainwindow.cpp" line="3024"/>
<source>Please reconsider endorsing MO2 on Nexus!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3056"/>
+ <location filename="mainwindow.cpp" line="3053"/>
<source>There is no supported sort mechanism for this game. You will probably have to use a third-party tool.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3153"/>
+ <location filename="mainwindow.cpp" line="3150"/>
<source>None of your %1 mods appear to have had recent file updates.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3166"/>
+ <location filename="mainwindow.cpp" line="3163"/>
<source>All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3396"/>
+ <location filename="mainwindow.cpp" line="3393"/>
<source>Thank you!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3397"/>
+ <location filename="mainwindow.cpp" line="3394"/>
<source>Thank you for your endorsement!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3511"/>
+ <location filename="mainwindow.cpp" line="3508"/>
<source>Mod ID %1 no longer seems to be available on Nexus.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3529"/>
+ <location filename="mainwindow.cpp" line="3526"/>
<source>Error %1: Request to Nexus failed: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3546"/>
- <location filename="mainwindow.cpp" line="3619"/>
+ <location filename="mainwindow.cpp" line="3543"/>
+ <location filename="mainwindow.cpp" line="3616"/>
<source>failed to read %1: %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3559"/>
+ <location filename="mainwindow.cpp" line="3556"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3560"/>
+ <location filename="mainwindow.cpp" line="3557"/>
<source>failed to extract %1 (errorcode %2)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3594"/>
+ <location filename="mainwindow.cpp" line="3591"/>
<source>Extract BSA</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3633"/>
+ <location filename="mainwindow.cpp" line="3630"/>
<source>This archive contains invalid hashes. Some files may be broken.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3643"/>
+ <location filename="mainwindow.cpp" line="3640"/>
<source>Extract...</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3712"/>
+ <location filename="mainwindow.cpp" line="3709"/>
<source>Remove &apos;%1&apos; from the toolbar</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3768"/>
+ <location filename="mainwindow.cpp" line="3765"/>
<source>Backup of load order created</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3779"/>
+ <location filename="mainwindow.cpp" line="3776"/>
<source>Choose backup to restore</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3796"/>
+ <location filename="mainwindow.cpp" line="3793"/>
<source>No Backups</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3797"/>
+ <location filename="mainwindow.cpp" line="3794"/>
<source>There are no backups to restore</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3821"/>
- <location filename="mainwindow.cpp" line="3845"/>
+ <location filename="mainwindow.cpp" line="3818"/>
+ <location filename="mainwindow.cpp" line="3842"/>
<source>Restore failed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3822"/>
- <location filename="mainwindow.cpp" line="3846"/>
+ <location filename="mainwindow.cpp" line="3819"/>
+ <location filename="mainwindow.cpp" line="3843"/>
<source>Failed to restore the backup. Errorcode: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3834"/>
+ <location filename="mainwindow.cpp" line="3831"/>
<source>Backup of mod list created</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3914"/>
+ <location filename="mainwindow.cpp" line="3911"/>
<source>A file with the same name has already been downloaded. What would you like to do?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3916"/>
+ <location filename="mainwindow.cpp" line="3913"/>
<source>Overwrite</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3917"/>
+ <location filename="mainwindow.cpp" line="3914"/>
<source>Rename new file</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="3918"/>
+ <location filename="mainwindow.cpp" line="3915"/>
<source>Ignore file</source>
<translation type="unfinished"></translation>
</message>
@@ -4256,17 +4256,17 @@ You will have to visit the mod page on the %1 Nexus site to change your mind.</s
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="modinfo.cpp" line="327"/>
+ <location filename="modinfo.cpp" line="328"/>
<source>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.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="modinfo.cpp" line="348"/>
+ <location filename="modinfo.cpp" line="349"/>
<source>All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="modinfo.cpp" line="352"/>
+ <location filename="modinfo.cpp" line="353"/>
<source>You have mods that haven&apos;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.</source>
<translation type="unfinished"></translation>
</message>
@@ -4693,12 +4693,12 @@ p, li { white-space: pre-wrap; }
<context>
<name>ModInfoRegular</name>
<message>
- <location filename="modinforegular.cpp" line="730"/>
+ <location filename="modinforegular.cpp" line="731"/>
<source>%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="modinforegular.cpp" line="736"/>
+ <location filename="modinforegular.cpp" line="737"/>
<source>Categories: &lt;br&gt;</source>
<translation type="unfinished"></translation>
</message>
@@ -5884,27 +5884,27 @@ Please enter a name:</source>
<context>
<name>OrganizerCore</name>
<message>
- <location filename="organizercore.cpp" line="198"/>
+ <location filename="organizercore.cpp" line="189"/>
<source>File is write protected</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="200"/>
+ <location filename="organizercore.cpp" line="191"/>
<source>Invalid file format (probably a bug)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="202"/>
+ <location filename="organizercore.cpp" line="193"/>
<source>Unknown error %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="206"/>
+ <location filename="organizercore.cpp" line="197"/>
<source>Failed to write settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="207"/>
+ <location filename="organizercore.cpp" line="198"/>
<source>An error occurred trying to write back MO settings to %1: %2</source>
<translation type="unfinished"></translation>
</message>
@@ -5924,167 +5924,167 @@ Please enter a name:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="743"/>
+ <location filename="organizercore.cpp" line="744"/>
<source>Installation cancelled</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="744"/>
+ <location filename="organizercore.cpp" line="745"/>
<source>Another installation is currently in progress.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="757"/>
+ <location filename="organizercore.cpp" line="758"/>
<source>Installation successful</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="790"/>
+ <location filename="organizercore.cpp" line="791"/>
<source>Configure Mod</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="791"/>
+ <location filename="organizercore.cpp" line="792"/>
<source>This mod contains ini tweaks. Do you want to configure them now?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="801"/>
+ <location filename="organizercore.cpp" line="802"/>
<source>mod not found: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="817"/>
+ <location filename="organizercore.cpp" line="818"/>
<source>Extraction cancelled</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="818"/>
+ <location filename="organizercore.cpp" line="819"/>
<source>The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1045"/>
+ <location filename="organizercore.cpp" line="1046"/>
<source>file not found: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1061"/>
+ <location filename="organizercore.cpp" line="1062"/>
<source>failed to generate preview for %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1109"/>
+ <location filename="organizercore.cpp" line="1110"/>
<source>Sorry</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1110"/>
+ <location filename="organizercore.cpp" line="1111"/>
<source>Sorry, can&apos;t preview anything. This function currently does not support extracting from bsas.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1121"/>
+ <location filename="organizercore.cpp" line="1122"/>
<source>File &apos;%1&apos; not found.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1129"/>
+ <location filename="organizercore.cpp" line="1130"/>
<source>Failed to generate preview for %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1242"/>
+ <location filename="organizercore.cpp" line="1243"/>
<source>Failed to refresh list of esps: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1352"/>
+ <location filename="organizercore.cpp" line="1353"/>
<source>Multiple esps/esls activated, please check that they don&apos;t conflict.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1423"/>
+ <location filename="organizercore.cpp" line="1424"/>
<source>You need to be logged in with Nexus</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1467"/>
+ <location filename="organizercore.cpp" line="1468"/>
<source>Download?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1468"/>
+ <location filename="organizercore.cpp" line="1469"/>
<source>A download has been started but no installed page plugin recognizes it.
If you download anyway no information (i.e. version) will be associated with the download.
Continue?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1679"/>
- <location filename="organizercore.cpp" line="1731"/>
+ <location filename="organizercore.cpp" line="1685"/>
+ <location filename="organizercore.cpp" line="1737"/>
<source>failed to update mod list: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1738"/>
- <location filename="organizercore.cpp" line="1755"/>
+ <location filename="organizercore.cpp" line="1744"/>
+ <location filename="organizercore.cpp" line="1761"/>
<source>login successful</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1764"/>
+ <location filename="organizercore.cpp" line="1770"/>
<source>Login failed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1765"/>
+ <location filename="organizercore.cpp" line="1771"/>
<source>Login failed, try again?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1773"/>
+ <location filename="organizercore.cpp" line="1779"/>
<source>login failed: %1. Download will not be associated with an account</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1781"/>
+ <location filename="organizercore.cpp" line="1787"/>
<source>login failed: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1791"/>
+ <location filename="organizercore.cpp" line="1797"/>
<source>login failed: %1. You need to log-in with Nexus to update MO.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1838"/>
+ <location filename="organizercore.cpp" line="1844"/>
<source>MO1 &quot;Script Extender&quot; load mechanism has left hook.dll in your game folder</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1842"/>
- <location filename="organizercore.cpp" line="1863"/>
+ <location filename="organizercore.cpp" line="1848"/>
+ <location filename="organizercore.cpp" line="1869"/>
<source>Description missing</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1851"/>
+ <location filename="organizercore.cpp" line="1857"/>
<source>&lt;a href=&quot;%1&quot;&gt;hook.dll&lt;/a&gt; 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 &quot;Script Extender&quot;, 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.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1888"/>
+ <location filename="organizercore.cpp" line="1894"/>
<source>failed to save load order: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="1957"/>
+ <location filename="organizercore.cpp" line="1963"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="organizercore.cpp" line="2037"/>
+ <location filename="organizercore.cpp" line="2044"/>
<source>The designated write target &quot;%1&quot; is not enabled.</source>
<translation type="unfinished"></translation>
</message>
@@ -6210,48 +6210,48 @@ Continue?</source>
<context>
<name>PluginContainer</name>
<message>
- <location filename="plugincontainer.cpp" line="1107"/>
+ <location filename="plugincontainer.cpp" line="1103"/>
<source>Plugin error</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1108"/>
+ <location filename="plugincontainer.cpp" line="1104"/>
<source>Mod Organizer failed to load the plugin &apos;%1&apos; last time it was started.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1111"/>
+ <location filename="plugincontainer.cpp" line="1107"/>
<source>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.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1116"/>
+ <location filename="plugincontainer.cpp" line="1112"/>
<source>Skip this plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1117"/>
+ <location filename="plugincontainer.cpp" line="1113"/>
<source>Blacklist this plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1118"/>
+ <location filename="plugincontainer.cpp" line="1114"/>
<source>Load this plugin</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1216"/>
+ <location filename="plugincontainer.cpp" line="1212"/>
<source>Some plugins could not be loaded</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1219"/>
- <location filename="plugincontainer.cpp" line="1239"/>
+ <location filename="plugincontainer.cpp" line="1215"/>
+ <location filename="plugincontainer.cpp" line="1235"/>
<source>Description missing</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="1229"/>
+ <location filename="plugincontainer.cpp" line="1225"/>
<source>The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:</source>
<translation type="unfinished"></translation>
</message>
@@ -6643,71 +6643,71 @@ p, li { white-space: pre-wrap; }
<context>
<name>Profile</name>
<message>
- <location filename="profile.cpp" line="84"/>
+ <location filename="profile.cpp" line="85"/>
<source>invalid profile name: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="88"/>
+ <location filename="profile.cpp" line="89"/>
<source>failed to create %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="276"/>
+ <location filename="profile.cpp" line="278"/>
<source>failed to write mod list: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="287"/>
+ <location filename="profile.cpp" line="289"/>
<source>failed to update tweaked ini file, wrong settings may be used: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="309"/>
+ <location filename="profile.cpp" line="311"/>
<source>failed to create tweaked ini: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="335"/>
+ <location filename="profile.cpp" line="337"/>
<source>failed to open %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="427"/>
+ <location filename="profile.cpp" line="429"/>
<source>&quot;%1&quot; is missing or inaccessible</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="489"/>
- <location filename="profile.cpp" line="527"/>
- <location filename="profile.cpp" line="619"/>
- <location filename="profile.cpp" line="677"/>
- <location filename="profile.cpp" line="686"/>
+ <location filename="profile.cpp" line="491"/>
+ <location filename="profile.cpp" line="529"/>
+ <location filename="profile.cpp" line="621"/>
+ <location filename="profile.cpp" line="679"/>
+ <location filename="profile.cpp" line="688"/>
<source>invalid mod index: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="572"/>
+ <location filename="profile.cpp" line="574"/>
<source>A mod named &quot;overwrite&quot; was detected, disabled, and moved to the highest priority on the mod list. You may want to rename this mod and enable it again.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="863"/>
+ <location filename="profile.cpp" line="865"/>
<source>Delete profile-specific save games?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="864"/>
+ <location filename="profile.cpp" line="866"/>
<source>Do you want to delete the profile-specific save games? (If you select &quot;No&quot;, the save games will show up again if you re-enable profile-specific save games)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="899"/>
+ <location filename="profile.cpp" line="901"/>
<source>Missing profile-specific game INI files!</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="900"/>
+ <location filename="profile.cpp" line="902"/>
<source>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:
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="919"/>
+ <location filename="profile.cpp" line="921"/>
<source>Delete profile-specific game INI files?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="920"/>
+ <location filename="profile.cpp" line="922"/>
<source>Do you want to delete the profile-specific game INI files? (If you select &quot;No&quot;, the INI files will be used again if you re-enable profile-specific game INI files.)</source>
<translation type="unfinished"></translation>
</message>
@@ -6890,103 +6890,103 @@ p, li { white-space: pre-wrap; }
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="77"/>
+ <location filename="profilesdialog.cpp" line="78"/>
<source>Archive invalidation isn&apos;t required for this game.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="83"/>
+ <location filename="profilesdialog.cpp" line="84"/>
<source>This game does not support profile-specific game saves.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="156"/>
- <location filename="profilesdialog.cpp" line="203"/>
+ <location filename="profilesdialog.cpp" line="157"/>
+ <location filename="profilesdialog.cpp" line="205"/>
<source>failed to create profile: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="211"/>
+ <location filename="profilesdialog.cpp" line="213"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="212"/>
+ <location filename="profilesdialog.cpp" line="214"/>
<source>Please enter a name for the new profile</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="221"/>
+ <location filename="profilesdialog.cpp" line="223"/>
<source>failed to copy profile: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="224"/>
+ <location filename="profilesdialog.cpp" line="226"/>
<source>Invalid name</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="224"/>
+ <location filename="profilesdialog.cpp" line="226"/>
<source>Invalid profile name</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="234"/>
+ <location filename="profilesdialog.cpp" line="236"/>
<source>Deleting active profile</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="235"/>
+ <location filename="profilesdialog.cpp" line="237"/>
<source>Unable to delete active profile. Please change to a different profile first.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="240"/>
+ <location filename="profilesdialog.cpp" line="242"/>
<source>Confirm</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="241"/>
+ <location filename="profilesdialog.cpp" line="243"/>
<source>Are you sure you want to remove this profile (including profile-specific save games, if any)?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="251"/>
+ <location filename="profilesdialog.cpp" line="253"/>
<source>Profile broken</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="252"/>
+ <location filename="profilesdialog.cpp" line="254"/>
<source>This profile you&apos;re about to delete seems to be broken or the path is invalid. I&apos;m about to delete the following folder: &quot;%1&quot;. Proceed?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="286"/>
+ <location filename="profilesdialog.cpp" line="288"/>
<source>Renaming active profile</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="287"/>
+ <location filename="profilesdialog.cpp" line="289"/>
<source>The active profile cannot be renamed. Please change to a different profile first.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="297"/>
+ <location filename="profilesdialog.cpp" line="299"/>
<source>Rename Profile</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="297"/>
+ <location filename="profilesdialog.cpp" line="299"/>
<source>New Name</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="335"/>
+ <location filename="profilesdialog.cpp" line="337"/>
<source>failed to change archive invalidation state: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="373"/>
+ <location filename="profilesdialog.cpp" line="375"/>
<source>failed to determine if invalidation is active: %1</source>
<translation type="unfinished"></translation>
</message>
@@ -7610,13 +7610,13 @@ Destination:<byte value="xd"/>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="1825"/>
- <location filename="mainwindow.cpp" line="2908"/>
+ <location filename="mainwindow.cpp" line="1824"/>
+ <location filename="mainwindow.cpp" line="2905"/>
<source>&lt;Manage...&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="mainwindow.cpp" line="1837"/>
+ <location filename="mainwindow.cpp" line="1836"/>
<source>failed to parse profile %1: %2</source>
<translation type="unfinished"></translation>
</message>
@@ -7759,7 +7759,7 @@ Destination:<byte value="xd"/>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="plugincontainer.cpp" line="855"/>
+ <location filename="plugincontainer.cpp" line="851"/>
<source>failed to initialize plugin %1: %2</source>
<translation type="unfinished"></translation>
</message>
@@ -7791,12 +7791,12 @@ Destination:<byte value="xd"/>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profile.cpp" line="68"/>
+ <location filename="profile.cpp" line="69"/>
<source>failed to create %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="profilesdialog.cpp" line="116"/>
+ <location filename="profilesdialog.cpp" line="117"/>
<source>Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile!</source>
<translation type="unfinished"></translation>
</message>
@@ -8297,27 +8297,27 @@ You can restart Mod Organizer as administrator and try launching the program aga
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../game_gamebryo/src/gamebryo/gamebryosavegame.cpp" line="46"/>
+ <location filename="../../game_gamebryo/src/gamebryo/gamebryosavegame.cpp" line="47"/>
<source>%1, #%2, Level %3, %4</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../game_gamebryo/src/gamebryo/gamebryosavegame.cpp" line="100"/>
+ <location filename="../../game_gamebryo/src/gamebryo/gamebryosavegame.cpp" line="101"/>
<source>failed to open %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../game_gamebryo/src/gamebryo/gamebryosavegame.cpp" line="110"/>
+ <location filename="../../game_gamebryo/src/gamebryo/gamebryosavegame.cpp" line="111"/>
<source>wrong file format - expected %1 got &apos;%2&apos; for %3</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../game_gamebryo/src/gamebryo/gamegamebryo.cpp" line="323"/>
+ <location filename="../../game_gamebryo/src/gamebryo/gamegamebryo.cpp" line="318"/>
<source>failed to query registry path (preflight): %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../../game_gamebryo/src/gamebryo/gamegamebryo.cpp" line="331"/>
+ <location filename="../../game_gamebryo/src/gamebryo/gamegamebryo.cpp" line="326"/>
<source>failed to query registry path (read): %1</source>
<translation type="unfinished"></translation>
</message>
@@ -8601,17 +8601,17 @@ p, li { white-space: pre-wrap; }
<context>
<name>SavesTab</name>
<message>
- <location filename="savestab.cpp" line="224"/>
+ <location filename="savestab.cpp" line="222"/>
<source>%1 more</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="savestab.cpp" line="228"/>
+ <location filename="savestab.cpp" line="226"/>
<source>Confirm</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location filename="savestab.cpp" line="229"/>
+ <location filename="savestab.cpp" line="227"/>
<source>Are you sure you want to remove the following %n save(s)?&lt;br&gt;&lt;ul&gt;%1&lt;/ul&gt;&lt;br&gt;Removed saves will be sent to the Recycle Bin.</source>
<translation type="unfinished">
<numerusform></numerusform>
@@ -8619,12 +8619,12 @@ p, li { white-space: pre-wrap; }
</translation>
</message>
<message>
- <location filename="savestab.cpp" line="252"/>
+ <location filename="savestab.cpp" line="250"/>
<source>Fix enabled mods...</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location filename="savestab.cpp" line="267"/>
+ <location filename="savestab.cpp" line="265"/>
<source>Delete %n save(s)</source>
<translation type="unfinished">
<numerusform></numerusform>
@@ -8632,7 +8632,7 @@ p, li { white-space: pre-wrap; }
</translation>
</message>
<message>
- <location filename="savestab.cpp" line="272"/>
+ <location filename="savestab.cpp" line="270"/>
<source>Open in Explorer...</source>
<translation type="unfinished"></translation>
</message>
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 2f45116c..df098205 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -89,10 +89,11 @@ QStringList toStringList(InputIterator current, InputIterator end)
}
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_UserInterface(nullptr), m_PluginContainer(nullptr), m_GamePlugin(nullptr),
+ m_CurrentProfile(nullptr), m_Settings(settings),
+ m_Updater(&NexusInterface::instance()), m_ModList(m_PluginContainer, this),
+ m_PluginList(*this),
+ 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 +136,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<ModDataContent>();
- if (contentFeature) {
- m_Contents = ModDataContentHolder(contentFeature->getAllContents());
- } else {
- m_Contents = ModDataContentHolder();
- }
- });
-
connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter,
&DelayedFileWriterBase::write);
@@ -273,6 +264,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 +454,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 +569,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<Profile>(QDir(profileDir), managedGame());
+ m_CurrentProfile =
+ std::make_unique<Profile>(QDir(profileDir), managedGame(), gameFeatures());
m_ModList.setProfile(m_CurrentProfile.get());
@@ -1249,7 +1250,7 @@ void OrganizerCore::refreshBSAList()
{
TimeThis tt("OrganizerCore::refreshBSAList()");
- DataArchives* archives = m_GamePlugin->feature<DataArchives>();
+ auto archives = gameFeatures().gameFeature<DataArchives>();
if (archives != nullptr) {
m_ArchivesInit = false;
@@ -1482,6 +1483,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 +1812,7 @@ void OrganizerCore::syncOverwrite()
QString OrganizerCore::oldMO1HookDll() const
{
- if (auto extender = managedGame()->feature<ScriptExtender>()) {
+ if (auto extender = gameFeatures().gameFeature<ScriptExtender>()) {
QString hookdll =
QDir::toNativeSeparators(managedGame()->dataDirectory().absoluteFilePath(
extender->PluginPath() + "/hook.dll"));
@@ -1999,7 +2005,8 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString& profileName,
}
IPluginGame* game = qApp->property("managed_game").value<IPluginGame*>();
- Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game);
+ Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game,
+ gameFeatures());
MappingType result;
@@ -2038,7 +2045,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString& profileName,
}
if (m_CurrentProfile->localSavesEnabled()) {
- LocalSavegames* localSaves = game->feature<LocalSavegames>();
+ auto localSaves = gameFeatures().gameFeature<LocalSavegames>();
if (localSaves != nullptr) {
MappingType saveMap =
localSaves->mappings(currentProfile()->absolutePath() + "/saves");
diff --git a/src/organizercore.h b/src/organizercore.h
index 5bf6f4b6..726babfd 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 @@ public:
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 @@ public:
/**
* @brief Construct a ModDataContentHold holding the given list of contents.
*/
- ModDataContentHolder(std::vector<ModDataContent::Content> contents)
+ ModDataContentHolder(std::vector<MOBase::ModDataContent::Content> contents)
: m_Contents(std::move(contents))
{}
@@ -275,6 +276,9 @@ public:
//
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 0c1fb409..6edbdbab 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<DownloadManagerProxy>(this, organizer->downloadManager())),
m_ModListProxy(std::make_unique<ModListProxy>(this, organizer->modList())),
m_PluginListProxy(
- std::make_unique<PluginListProxy>(this, organizer->pluginList()))
+ std::make_unique<PluginListProxy>(this, organizer->pluginList())),
+ m_GameFeaturesProxy(
+ std::make_unique<GameFeaturesProxy>(this, 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 75146432..fbae7cde 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 @@ public: // IOrganizer interface
const std::function<bool(const FileInfo&)>& filter) const override;
virtual std::shared_ptr<const MOBase::IFileTree> 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 @@ private:
std::unique_ptr<DownloadManagerProxy> m_DownloadManagerProxy;
std::unique_ptr<ModListProxy> m_ModListProxy;
std::unique_ptr<PluginListProxy> m_PluginListProxy;
+ std::unique_ptr<GameFeaturesProxy> m_GameFeaturesProxy;
};
#endif // ORGANIZERPROXY_H
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index 157d3399..c73d4566 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -320,7 +320,9 @@ void PluginRequirements::requiredFor(std::vector<MOBase::IPlugin*>& 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<GameFeatures>(organizer, this)),
+ m_PreviewGenerator(*this)
{}
PluginContainer::~PluginContainer()
@@ -625,12 +627,12 @@ 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
// a const IPlugin* instead, but there are a few tricks with qobject_cast and const.
- return const_cast<IPluginGame*>(m_Organizer->managedGame());
+ return m_Organizer ? const_cast<IPluginGame*>(m_Organizer->managedGame()) : nullptr;
}
bool PluginContainer::isEnabled(IPlugin* plugin) const
@@ -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<QObject*>& plugins) const
{
// setUserInterface()
diff --git a/src/plugincontainer.h b/src/plugincontainer.h
index d5ceca72..4854954d 100644
--- a/src/plugincontainer.h
+++ b/src/plugincontainer.h
@@ -24,6 +24,8 @@ class IUserInterface;
#include <memory>
#include <vector>
+#include "game_features.h"
+
class OrganizerProxy;
/**
@@ -279,7 +281,7 @@ public:
/**
* @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.
@@ -338,9 +340,14 @@ public:
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 @@ private:
// Main user interface, can be null until MW has been initialized.
IUserInterface* m_UserInterface;
+ // Game features
+ std::unique_ptr<GameFeatures> 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 b1fd9757..748ffbad 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>();
+ auto gamePlugins = m_Organizer.gameFeatures().gameFeature<GamePlugins>();
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>();
+ auto gamePlugins = m_Organizer.gameFeatures().gameFeature<GamePlugins>();
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>();
+ auto gamePlugins = m_Organizer.gameFeatures().gameFeature<GamePlugins>();
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<GamePlugins>() && esp.hasLightExtension &&
- !m_GamePlugin->feature<GamePlugins>()->lightPluginsAreSupported()) {
+ auto feature = m_Organizer.gameFeatures().gameFeature<GamePlugins>();
+ if (feature && esp.hasLightExtension && feature->lightPluginsAreSupported()) {
toolTip += "<br><br>" + tr("Light plugins (ESL) are not supported by this game.");
} else {
toolTip += "<br><br>" + tr("This game does not currently permit custom plugin "
diff --git a/src/profile.cpp b/src/profile.cpp
index d4299a7b..a0c5a2d4 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#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<BSAInvalidation>();
- DataArchives* dataArchives = m_GamePlugin->feature<DataArchives>();
- bool found = false;
+ auto invalidation = m_GameFeatures.gameFeature<BSAInvalidation>();
+ auto dataArchives = m_GameFeatures.gameFeature<DataArchives>();
+ 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<BSAInvalidation>();
- DataArchives* dataArchives = m_GamePlugin->feature<DataArchives>();
+ auto invalidation = m_GameFeatures.gameFeature<BSAInvalidation>();
+ auto dataArchives = m_GameFeatures.gameFeature<DataArchives>();
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<BSAInvalidation>();
+ auto invalidation = m_GameFeatures.gameFeature<BSAInvalidation>();
if (invalidation != nullptr) {
invalidation->deactivate(this);
@@ -836,7 +838,7 @@ void Profile::deactivateInvalidation()
void Profile::activateInvalidation()
{
- BSAInvalidation* invalidation = m_GamePlugin->feature<BSAInvalidation>();
+ auto invalidation = m_GameFeatures.gameFeature<BSAInvalidation>();
if (invalidation != nullptr) {
invalidation->activate(this);
diff --git a/src/profile.h b/src/profile.h
index 0adb4d0f..fcce75ee 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 @@ public:
* @param filter save game filter. Defaults to &lt;no filter&gt;.
**/
Profile(const QString& name, MOBase::IPluginGame const* gamePlugin,
- bool useDefaultSettings);
+ GameFeatures const& features, bool useDefaultSettings);
/**
* @brief constructor
@@ -80,7 +82,8 @@ public:
*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 @@ private:
QSettings* m_Settings;
+ const GameFeatures& m_GameFeatures;
const MOBase::IPluginGame* m_GamePlugin;
std::vector<ModStatus> m_ModStatus;
diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp
index f8010ada..24eddc20 100644
--- a/src/profilesdialog.cpp
+++ b/src/profilesdialog.cpp
@@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#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<BSAInvalidation>();
-
+ auto invalidation = m_GameFeatures.gameFeature<BSAInvalidation>();
if (invalidation == nullptr) {
ui->invalidationBox->setToolTip(
tr("Archive invalidation isn't required for this game."));
ui->invalidationBox->setEnabled(false);
}
- if (!m_Game->feature<LocalSavegames>()) {
+ if (!m_GameFeatures.gameFeature<LocalSavegames>()) {
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 dc8329a2..1a2a36e2 100644
--- a/src/profilesdialog.h
+++ b/src/profilesdialog.h
@@ -20,13 +20,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef PROFILESDIALOG_H
#define PROFILESDIALOG_H
+#include <QObject>
+
#include "tutorabledialog.h"
-class Profile;
-class OrganizerCore;
class QListWidget;
class QListWidgetItem;
-#include <QObject>
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 813fea05..2cbd17de 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<SaveGameInfo>();
+ auto info = m_core.gameFeatures().gameFeature<SaveGameInfo>();
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>();
+ auto info = m_core.gameFeatures().gameFeature<SaveGameInfo>();
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>();
+ auto info = m_core.gameFeatures().gameFeature<SaveGameInfo>();
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<SaveGameInfo>();
+ auto info = m_core.gameFeatures().gameFeature<SaveGameInfo>();
const auto sel = ui.list->selectionModel()->selectedRows();
if (sel.empty()) {
diff --git a/src/savestab.h b/src/savestab.h
index a903e4fb..6a32daf9 100644
--- a/src/savestab.h
+++ b/src/savestab.h
@@ -58,7 +58,7 @@ private:
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();
};