diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1b9a0f852..8faff52ca 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -55,6 +55,7 @@ mo2_add_filter(NAME src/core GROUPS
nexusinterface
nxmaccessmanager
organizercore
+ game_features
plugincontainer
apiuseraccount
processrunner
@@ -196,6 +197,7 @@ mo2_add_filter(NAME src/profiles GROUPS
mo2_add_filter(NAME src/proxies GROUPS
downloadmanagerproxy
+ gamefeaturesproxy
modlistproxy
organizerproxy
pluginlistproxy
diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp
index 67ad5b483..a95e78d39 100644
--- a/src/activatemodsdialog.cpp
+++ b/src/activatemodsdialog.cpp
@@ -28,6 +28,8 @@ along with Mod Organizer. If not, see .
#include
+using namespace MOBase;
+
ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets,
QWidget* parent)
: TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog)
diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h
index f7fd719f5..ea9a11483 100644
--- a/src/activatemodsdialog.h
+++ b/src/activatemodsdialog.h
@@ -24,9 +24,8 @@ along with Mod Organizer. If not, see .
#include "tutorabledialog.h"
#include
-
-class QString;
-class QWidget;
+#include
+#include
#include
@@ -49,7 +48,7 @@ class ActivateModsDialog : public MOBase::TutorableDialog
* @param missingPlugins a map containing missing plugins that need to be activated
* @param parent ... Defaults to 0.
**/
- explicit ActivateModsDialog(SaveGameInfo::MissingAssets const& missingAssets,
+ explicit ActivateModsDialog(MOBase::SaveGameInfo::MissingAssets const& missingAssets,
QWidget* parent = 0);
~ActivateModsDialog();
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp
index 3313b8c5e..e2e05b2cc 100644
--- a/src/directoryrefresher.cpp
+++ b/src/directoryrefresher.cpp
@@ -22,9 +22,11 @@ along with Mod Organizer. If not, see .
#include "shared/filesorigin.h"
#include "envfs.h"
+#include "game_features.h"
#include "iplugingame.h"
#include "modinfo.h"
#include "modinfodialogfwd.h"
+#include "organizercore.h"
#include "report.h"
#include "settings.h"
#include "shared/util.h"
@@ -159,8 +161,8 @@ void dumpStats(std::vector& stats)
++run;
}
-DirectoryRefresher::DirectoryRefresher(std::size_t threadCount)
- : m_threadCount(threadCount), m_lastFileCount(0)
+DirectoryRefresher::DirectoryRefresher(OrganizerCore* core, std::size_t threadCount)
+ : m_Core(*core), m_threadCount(threadCount), m_lastFileCount(0)
{}
DirectoryEntry* DirectoryRefresher::stealDirectoryStructure()
@@ -204,11 +206,9 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry* root,
const QString& directory,
const QStringList& archives)
{
- const IPluginGame* game = qApp->property("managed_game").value();
-
QStringList loadOrder;
- GamePlugins* gamePlugins = game->feature();
+ auto gamePlugins = m_Core.gameFeatures().gameFeature();
if (gamePlugins) {
loadOrder = gamePlugins->getLoadOrder();
}
@@ -320,6 +320,7 @@ void DirectoryRefresher::addModToStructure(DirectoryEntry* directoryStructure,
struct ModThread
{
+ GameFeatures* gameFeatures;
DirectoryRefreshProgress* progress = nullptr;
DirectoryEntry* ds = nullptr;
std::wstring modName;
@@ -355,10 +356,8 @@ struct ModThread
ds->addFromOrigin(walker, modName, path, prio, *stats);
if (Settings::instance().archiveParsing()) {
- const IPluginGame* game = qApp->property("managed_game").value();
-
QStringList loadOrder;
- GamePlugins* gamePlugins = game->feature();
+ auto gamePlugins = gameFeatures->gameFeature();
if (gamePlugins) {
loadOrder = gamePlugins->getLoadOrder();
}
@@ -420,11 +419,12 @@ void DirectoryRefresher::addMultipleModsFilesToStructure(
} else {
auto& mt = g_threads.request();
- mt.progress = progress;
- mt.ds = directoryStructure;
- mt.modName = e.modName.toStdWString();
- mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
- mt.prio = prio;
+ mt.gameFeatures = &m_Core.gameFeatures();
+ mt.progress = progress;
+ mt.ds = directoryStructure;
+ mt.modName = e.modName.toStdWString();
+ mt.path = QDir::toNativeSeparators(e.absolutePath).toStdWString();
+ mt.prio = prio;
mt.archives.clear();
for (auto&& a : e.archives) {
diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h
index 2e6de1a06..771aaa952 100644
--- a/src/directoryrefresher.h
+++ b/src/directoryrefresher.h
@@ -30,6 +30,8 @@ along with Mod Organizer. If not, see .
#include
#include
+class OrganizerCore;
+
/**
* @brief used to asynchronously generate the virtual view of the combined data
*directory
@@ -54,7 +56,7 @@ class DirectoryRefresher : public QObject
int priority;
};
- DirectoryRefresher(std::size_t threadCount);
+ DirectoryRefresher(OrganizerCore* core, std::size_t threadCount);
/**
* @brief retrieve the updated directory structure
@@ -146,6 +148,8 @@ public slots:
void refreshed();
private:
+ OrganizerCore& m_Core;
+
std::vector m_Mods;
std::set m_EnabledArchives;
std::unique_ptr m_Root;
diff --git a/src/game_features.cpp b/src/game_features.cpp
new file mode 100644
index 000000000..bc083bc80
--- /dev/null
+++ b/src/game_features.cpp
@@ -0,0 +1,340 @@
+#include "game_features.h"
+
+#include
+
+#include
+
+#include "iplugingame.h"
+#include "log.h"
+
+#include "bsainvalidation.h"
+#include "dataarchives.h"
+#include "gameplugins.h"
+#include "localsavegames.h"
+#include "organizercore.h"
+#include "plugincontainer.h"
+#include "savegameinfo.h"
+#include "scriptextender.h"
+#include "unmanagedmods.h"
+
+using namespace MOBase;
+
+// avoid updating features more than once every 100ms
+constexpr std::chrono::milliseconds UPDATE_QUEUE_TIMER{100};
+
+// specific type index for checker and content that behaves differently
+const std::type_index ModDataCheckerIndex{typeid(ModDataChecker)};
+const std::type_index ModDataContentIndex{typeid(ModDataContent)};
+
+class GameFeatures::CombinedModDataChecker : public ModDataChecker
+{
+ std::vector> m_modDataCheckers;
+ mutable std::shared_ptr m_fixer = nullptr;
+
+public:
+ void setCheckers(std::vector> checkers)
+ {
+ m_modDataCheckers = std::move(checkers);
+ m_fixer = nullptr;
+ }
+
+ bool isValid() const { return !m_modDataCheckers.empty(); }
+
+ CheckReturn
+ dataLooksValid(std::shared_ptr 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
+ fix(std::shared_ptr fileTree) const override
+ {
+ if (m_fixer) {
+ return m_fixer->fix(fileTree);
+ }
+
+ return nullptr;
+ }
+};
+class GameFeatures::CombinedModDataContent : public ModDataContent
+{
+ // store the ModDataContent and the offset to add to the content
+ std::vector, int>> m_modDataContents;
+ std::vector m_allContents;
+
+public:
+ bool isValid() const { return !m_modDataContents.empty(); }
+
+ void setContents(std::vector> modDataContents)
+ {
+ m_modDataContents.clear();
+ m_modDataContents.reserve(modDataContents.size());
+
+ m_allContents.clear();
+
+ // update all contents and offsets
+ std::size_t offset = 0;
+ for (auto& modDataContent : modDataContents) {
+
+ m_modDataContents.emplace_back(modDataContent, static_cast(offset));
+
+ // add to the list of contents
+ auto contents = modDataContent->getAllContents();
+ m_allContents.insert(m_allContents.end(),
+ std::make_move_iterator(contents.begin()),
+ std::make_move_iterator(contents.end()));
+
+ // increase offset for next mod data content
+
+ offset += contents.size();
+ }
+ }
+
+ std::vector getAllContents() const override { return m_allContents; }
+
+ std::vector
+ getContentsFor(std::shared_ptr fileTree) const
+ {
+ std::vector contentsFor;
+ for (auto& modDataContent : m_modDataContents) {
+ auto contentsForFrom = modDataContent.first->getContentsFor(fileTree);
+ std::transform(contentsForFrom.begin(), contentsForFrom.end(),
+ std::back_inserter(contentsFor),
+ [offset = modDataContent.second](auto content) {
+ return content + offset;
+ });
+ }
+
+ return contentsFor;
+ }
+};
+
+GameFeatures::GameFeatures(OrganizerCore* core, PluginContainer* plugins)
+ : m_pluginContainer(*plugins),
+ m_modDataChecker(std::make_unique()),
+ m_modDataContent(std::make_unique())
+{
+ // can be nullptr since the plugin container can be initialized with a Core (e.g.,
+ // on first MO2 start)
+ if (core) {
+ connect(core, &OrganizerCore::managedGameChanged, [this] {
+ updateCurrentFeatures();
+ });
+ }
+
+ auto updateFeatures = [this] {
+ QTimer::singleShot(UPDATE_QUEUE_TIMER, [this] {
+ updateCurrentFeatures();
+ });
+ };
+
+ connect(plugins, &PluginContainer::pluginEnabled, updateFeatures);
+ connect(plugins, &PluginContainer::pluginDisabled, updateFeatures);
+ connect(plugins, &PluginContainer::pluginRegistered, updateFeatures);
+ connect(plugins, &PluginContainer::pluginUnregistered,
+ [this, updateFeatures](MOBase::IPlugin* plugin) {
+ // remove features from the current plugin
+ for (auto& [_, features] : m_allFeatures) {
+ features.erase(std::remove_if(features.begin(), features.end(),
+ [plugin](const auto& feature) {
+ return feature.plugin() == plugin;
+ }),
+ features.end());
+ }
+
+ // update current features
+ updateFeatures();
+ });
+}
+
+GameFeatures::~GameFeatures() {}
+
+GameFeatures::CombinedModDataChecker& GameFeatures::modDataChecker() const
+{
+ return dynamic_cast(*m_modDataChecker);
+}
+GameFeatures::CombinedModDataContent& GameFeatures::modDataContent() const
+{
+ return dynamic_cast(*m_modDataContent);
+}
+
+void GameFeatures::updateCurrentFeatures(std::type_index const& index)
+{
+ auto& features = m_allFeatures[index];
+
+ m_currentFeatures[index].clear();
+
+ // 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> checkers;
+ checkers.reserve(currentCheckers.size());
+ std::transform(currentCheckers.begin(), currentCheckers.end(),
+ std::back_inserter(checkers), [](auto const& checker) {
+ return std::dynamic_pointer_cast(checker);
+ });
+ modDataChecker().setCheckers(std::move(checkers));
+ emit modDataCheckerUpdated(gameFeature().get());
+ }
+
+ // update mod data content
+ if (index == ModDataContentIndex) {
+ auto& currentContents = m_currentFeatures[ModDataContentIndex];
+ std::vector> contents;
+ contents.reserve(currentContents.size());
+ std::transform(currentContents.begin(), currentContents.end(),
+ std::back_inserter(contents), [](auto const& checker) {
+ return std::dynamic_pointer_cast(checker);
+ });
+ modDataContent().setContents(std::move(contents));
+ emit modDataContentUpdated(gameFeature().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 feature,
+ int priority)
+{
+ auto& features = m_allFeatures[feature->typeInfo()];
+
+ if (std::find_if(features.begin(), features.end(), [&feature](const auto& data) {
+ return data.feature() == feature;
+ }) != features.end()) {
+ log::error("cannot register feature multiple time");
+ return false;
+ }
+
+ std::decay_t::iterator it;
+ if (dynamic_cast(plugin)) {
+ it = features.end();
+ } else {
+ it = std::lower_bound(features.begin(), features.end(), priority,
+ [](const auto& feature, int priority) {
+ return feature.priority() > priority;
+ });
+ }
+
+ features.emplace(it, feature, plugin, games, std::numeric_limits::min());
+
+ // TODO: only update if relevant
+ updateCurrentFeatures(feature->typeInfo());
+
+ return true;
+}
+
+// unregister game features
+//
+bool GameFeatures::unregisterGameFeature(std::shared_ptr feature)
+{
+ bool removed = false;
+ for (auto& [_, features] : m_allFeatures) {
+ auto it =
+ std::find_if(features.begin(), features.end(), [&feature](const auto& data) {
+ return data.feature() == feature;
+ });
+
+ // the feature can only exist for one kind of features and cannot be duplicated
+ if (it != features.end()) {
+ features.erase(it);
+ removed = true;
+ break;
+ }
+ }
+
+ if (removed) {
+ updateCurrentFeatures();
+ }
+
+ return removed;
+}
+
+int GameFeatures::unregisterGameFeatures(MOBase::IPlugin* plugin,
+ std::type_info const& info)
+{
+ auto& features = m_allFeatures[info];
+
+ const auto initialSize = features.size();
+
+ features.erase(std::remove_if(features.begin(), features.end(),
+ [plugin](const auto& feature) {
+ return feature.plugin() == plugin;
+ }),
+ features.end());
+
+ const int removed = features.size() - initialSize;
+
+ if (removed) {
+ updateCurrentFeatures();
+ }
+
+ return removed;
+}
+
+std::shared_ptr 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 000000000..55f35e377
--- /dev/null
+++ b/src/game_features.h
@@ -0,0 +1,117 @@
+#ifndef MO2_GAME_FEATURES_H
+#define MO2_GAME_FEATURES_H
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "game_feature.h"
+#include "moddatachecker.h"
+#include "moddatacontent.h"
+
+namespace MOBase
+{
+class IPlugin;
+class IPluginGame;
+} // namespace MOBase
+
+class OrganizerCore;
+class PluginContainer;
+
+/**
+ * Class managing game features, either registered or from the game plugin.
+ */
+class GameFeatures : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ *
+ */
+ GameFeatures(OrganizerCore* core, PluginContainer* plugins);
+
+ ~GameFeatures();
+
+ // register game features
+ //
+ bool registerGameFeature(MOBase::IPlugin* plugin, QStringList const& games,
+ std::shared_ptr feature, int priority);
+
+ // unregister game features
+ //
+ bool unregisterGameFeature(std::shared_ptr feature);
+ int unregisterGameFeatures(MOBase::IPlugin* plugin, std::type_info const& info);
+
+ // retrieve a game feature
+ //
+ template
+ std::shared_ptr gameFeature() const
+ {
+ return std::dynamic_pointer_cast(gameFeature(typeid(T)));
+ }
+
+signals:
+ void modDataCheckerUpdated(const MOBase::ModDataChecker* check);
+ void modDataContentUpdated(const MOBase::ModDataContent* check);
+
+private:
+ friend class GameFeaturesProxy;
+
+ class GameFeatureWithData
+ {
+ // feature
+ std::shared_ptr m_feature;
+
+ // plugin that registered the feature
+ MOBase::IPlugin* m_plugin;
+
+ // games this plugin applies to - empty list indicates all games
+ QStringList m_games;
+
+ // priority of the plugin
+ int m_priority;
+
+ public:
+ GameFeatureWithData(std::shared_ptr feature,
+ MOBase::IPlugin* plugin, QStringList games, int priority)
+ : m_feature(feature), m_plugin(plugin), m_games(games), m_priority(priority)
+ {}
+
+ const auto& feature() const { return m_feature; }
+ auto* const plugin() const { return m_plugin; }
+ const auto& games() const { return m_games; }
+ auto priority() const { return m_priority; }
+ };
+
+ // retrieve a game feature from info
+ //
+ std::shared_ptr gameFeature(std::type_info const& index) const;
+
+ // update current features by filtering
+ //
+ void updateCurrentFeatures(std::type_index const& index);
+ void updateCurrentFeatures();
+
+ // implementation details
+ class CombinedModDataChecker;
+ class CombinedModDataContent;
+
+ CombinedModDataChecker& modDataChecker() const;
+ CombinedModDataContent& modDataContent() const;
+
+ PluginContainer& m_pluginContainer;
+
+ std::unordered_map> m_allFeatures;
+ std::unordered_map>>
+ m_currentFeatures;
+
+ std::shared_ptr m_modDataChecker;
+ std::shared_ptr m_modDataContent;
+};
+
+#endif
diff --git a/src/gamefeaturesproxy.cpp b/src/gamefeaturesproxy.cpp
new file mode 100644
index 000000000..3adac3539
--- /dev/null
+++ b/src/gamefeaturesproxy.cpp
@@ -0,0 +1,48 @@
+#include "gamefeaturesproxy.h"
+
+#include "game_features.h"
+#include "organizerproxy.h"
+
+GameFeaturesProxy::GameFeaturesProxy(OrganizerProxy* coreProxy,
+ GameFeatures& gameFeatures)
+ : m_CoreProxy(*coreProxy), m_Features(gameFeatures)
+{}
+
+bool GameFeaturesProxy::registerFeature(QStringList const& games,
+ std::shared_ptr feature,
+ int priority, bool replace)
+{
+ if (replace) {
+ m_Features.unregisterGameFeatures(m_CoreProxy.plugin(), feature->typeInfo());
+ }
+ return m_Features.registerGameFeature(m_CoreProxy.plugin(), games, feature, priority);
+}
+
+bool GameFeaturesProxy::registerFeature(MOBase::IPluginGame* game,
+ std::shared_ptr feature,
+ int priority, bool replace)
+{
+ return registerFeature({game->gameName()}, feature, priority, replace);
+}
+
+bool GameFeaturesProxy::registerFeature(std::shared_ptr feature,
+ int priority, bool replace)
+{
+ return registerFeature({}, feature, priority, replace);
+}
+
+bool GameFeaturesProxy::unregisterFeature(std::shared_ptr feature)
+{
+ return m_Features.unregisterGameFeature(feature);
+}
+
+std::shared_ptr
+GameFeaturesProxy::gameFeatureImpl(std::type_info const& info) const
+{
+ return m_Features.gameFeature(info);
+}
+
+int GameFeaturesProxy::unregisterFeaturesImpl(std::type_info const& info)
+{
+ return m_Features.unregisterGameFeatures(m_CoreProxy.plugin(), info);
+}
diff --git a/src/gamefeaturesproxy.h b/src/gamefeaturesproxy.h
new file mode 100644
index 000000000..9ffd6b046
--- /dev/null
+++ b/src/gamefeaturesproxy.h
@@ -0,0 +1,34 @@
+#ifndef GAMEFEATURESPROXY_H
+#define GAMEFEATURESPROXY_H
+
+#include "igamefeatures.h"
+
+class GameFeatures;
+class OrganizerProxy;
+
+class GameFeaturesProxy : public MOBase::IGameFeatures
+{
+public:
+ GameFeaturesProxy(OrganizerProxy* coreProxy, GameFeatures& gameFeatures);
+
+ bool registerFeature(QStringList const& games,
+ std::shared_ptr feature, int priority,
+ bool replace) override;
+ bool registerFeature(MOBase::IPluginGame* game,
+ std::shared_ptr feature, int priority,
+ bool replace) override;
+ bool registerFeature(std::shared_ptr feature, int priority,
+ bool replace) override;
+ bool unregisterFeature(std::shared_ptr feature) override;
+
+protected:
+ std::shared_ptr
+ gameFeatureImpl(std::type_info const& info) const override;
+ int unregisterFeaturesImpl(std::type_info const& info) override;
+
+private:
+ GameFeatures& m_Features;
+ OrganizerProxy& m_CoreProxy;
+};
+
+#endif
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index ca738c788..b14a14a1b 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -332,11 +332,11 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore,
m_SavesTab.reset(new SavesTab(this, m_OrganizerCore, ui));
// Hide stuff we do not need:
- IPluginGame const* game = m_OrganizerCore.managedGame();
- if (!game->feature()) {
+ auto& features = m_OrganizerCore.gameFeatures();
+ if (!features.gameFeature()) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->espTab));
}
- if (!game->feature()) {
+ if (!features.gameFeature()) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->bsaTab));
}
@@ -1796,15 +1796,14 @@ void MainWindow::on_profileBox_currentIndexChanged(int index)
activateSelectedProfile();
- LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature();
+ auto saveGames = m_OrganizerCore.gameFeatures().gameFeature();
if (saveGames != nullptr) {
if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) {
m_SavesTab->refreshSaveList();
}
}
- BSAInvalidation* invalidation =
- m_OrganizerCore.managedGame()->feature();
+ auto invalidation = m_OrganizerCore.gameFeatures().gameFeature();
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> items;
- BSAInvalidation* invalidation =
- m_OrganizerCore.managedGame()->feature();
+ auto invalidation = m_OrganizerCore.gameFeatures().gameFeature();
std::vector 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();
+ auto archives = m_OrganizerCore.gameFeatures().gameFeature();
if (archives != nullptr) {
ui->bsaList->blockSignals(true);
@@ -2384,15 +2382,14 @@ void MainWindow::on_actionAdd_Profile_triggered()
}
}
- LocalSavegames* saveGames = m_OrganizerCore.managedGame()->feature();
+ auto saveGames = m_OrganizerCore.gameFeatures().gameFeature();
if (saveGames != nullptr) {
if (saveGames->prepareProfile(m_OrganizerCore.currentProfile())) {
m_SavesTab->refreshSaveList();
}
}
- BSAInvalidation* invalidation =
- m_OrganizerCore.managedGame()->feature();
+ auto invalidation = m_OrganizerCore.gameFeatures().gameFeature();
if (invalidation != nullptr) {
if (invalidation->prepareProfile(m_OrganizerCore.currentProfile())) {
QTimer::singleShot(5, [this] {
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index 1028c66f1..abfd9de6c 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -249,8 +249,9 @@ void ModInfo::updateFromDisc(const QString& modsDirectory, OrganizerCore& core,
}
}
- auto* game = core.managedGame();
- UnmanagedMods* unmanaged = game->feature();
+ auto* game = core.managedGame();
+ auto& features = core.pluginContainer().gameFeatures();
+ auto unmanaged = features.gameFeature();
if (unmanaged != nullptr) {
for (const QString& modName : unmanaged->mods(!displayForeign)) {
ModInfo::EModType modType =
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index c79fc5740..d3ff65472 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -703,7 +703,8 @@ std::vector ModInfoRegular::getFlags() const
std::set ModInfoRegular::doGetContents() const
{
- ModDataContent* contentFeature = m_Core.managedGame()->feature();
+ auto contentFeature =
+ m_Core.pluginContainer().gameFeatures().gameFeature();
if (contentFeature) {
auto result = contentFeature->getContentsFor(fileTree());
diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp
index 8d4963917..2300405a4 100644
--- a/src/modinfowithconflictinfo.cpp
+++ b/src/modinfowithconflictinfo.cpp
@@ -303,7 +303,7 @@ void ModInfoWithConflictInfo::prefetch()
bool ModInfoWithConflictInfo::doIsValid() const
{
- auto mdc = m_Core.managedGame()->feature();
+ auto mdc = m_Core.gameFeatures().gameFeature();
if (mdc) {
auto qdirfiletree = fileTree();
diff --git a/src/organizer_en.ts b/src/organizer_en.ts
index 1295a3620..362ccd892 100644
--- a/src/organizer_en.ts
+++ b/src/organizer_en.ts
@@ -170,7 +170,7 @@ p, li { white-space: pre-wrap; }
-
+
@@ -3261,7 +3261,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi
-
+
@@ -3653,7 +3653,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi
-
+
@@ -3950,281 +3950,281 @@ As a final option, you can disable Nexus category mapping altogether, which can
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
@@ -4256,17 +4256,17 @@ You will have to visit the mod page on the %1 Nexus site to change your mind.
-
+
-
+
-
+
@@ -4693,12 +4693,12 @@ p, li { white-space: pre-wrap; }
ModInfoRegular
-
+
-
+
@@ -5884,27 +5884,27 @@ Please enter a name:
OrganizerCore
-
+
-
+
-
+
-
+
-
+
@@ -5924,167 +5924,167 @@ Please enter a name:
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
@@ -6210,48 +6210,48 @@ Continue?
PluginContainer
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
@@ -6643,71 +6643,71 @@ p, li { white-space: pre-wrap; }
Profile
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -6890,103 +6890,103 @@ p, li { white-space: pre-wrap; }
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -7610,13 +7610,13 @@ Destination:
-
-
+
+
-
+
@@ -7759,7 +7759,7 @@ Destination:
-
+
@@ -7791,12 +7791,12 @@ Destination:
-
+
-
+
@@ -8297,27 +8297,27 @@ You can restart Mod Organizer as administrator and try launching the program aga
-
+
-
+
-
+
-
+
-
+
@@ -8601,17 +8601,17 @@ p, li { white-space: pre-wrap; }
SavesTab
-
+
-
+
-
+
@@ -8619,12 +8619,12 @@ p, li { white-space: pre-wrap; }
-
+
-
+
@@ -8632,7 +8632,7 @@ p, li { white-space: pre-wrap; }
-
+
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 2f45116ce..df098205a 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();
- 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(QDir(profileDir), managedGame());
+ m_CurrentProfile =
+ std::make_unique(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();
+ auto archives = gameFeatures().gameFeature();
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()) {
+ if (auto extender = gameFeatures().gameFeature()) {
QString hookdll =
QDir::toNativeSeparators(managedGame()->dataDirectory().absoluteFilePath(
extender->PluginPath() + "/hook.dll"));
@@ -1999,7 +2005,8 @@ std::vector OrganizerCore::fileMapping(const QString& profileName,
}
IPluginGame* game = qApp->property("managed_game").value();
- Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game);
+ Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game,
+ gameFeatures());
MappingType result;
@@ -2038,7 +2045,7 @@ std::vector OrganizerCore::fileMapping(const QString& profileName,
}
if (m_CurrentProfile->localSavesEnabled()) {
- LocalSavegames* localSaves = game->feature();
+ auto localSaves = gameFeatures().gameFeature();
if (localSaves != nullptr) {
MappingType saveMap =
localSaves->mappings(currentProfile()->absolutePath() + "/saves");
diff --git a/src/organizercore.h b/src/organizercore.h
index 5bf6f4b64..726babfdf 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -39,6 +39,7 @@ class ModListSortProxy;
class PluginListSortProxy;
class Profile;
class IUserInterface;
+class GameFeatures;
class PluginContainer;
class DirectoryRefresher;
@@ -106,7 +107,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose
struct ModDataContentHolder
{
- using Content = ModDataContent::Content;
+ using Content = MOBase::ModDataContent::Content;
/**
* @return true if the hold list of contents is empty, false otherwise.
@@ -205,7 +206,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose
/**
* @brief Construct a ModDataContentHold holding the given list of contents.
*/
- ModDataContentHolder(std::vector contents)
+ ModDataContentHolder(std::vector contents)
: m_Contents(std::move(contents))
{}
@@ -275,6 +276,9 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose
//
PluginContainer& pluginContainer() const;
+ // return the game features
+ GameFeatures& gameFeatures() const;
+
MOBase::IPluginGame const* managedGame() const;
/**
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp
index 0c1fb4099..6edbdbab7 100644
--- a/src/organizerproxy.cpp
+++ b/src/organizerproxy.cpp
@@ -1,6 +1,7 @@
#include "organizerproxy.h"
#include "downloadmanagerproxy.h"
+#include "gamefeaturesproxy.h"
#include "glob_matching.h"
#include "modlistproxy.h"
#include "organizercore.h"
@@ -25,7 +26,9 @@ OrganizerProxy::OrganizerProxy(OrganizerCore* organizer,
std::make_unique(this, organizer->downloadManager())),
m_ModListProxy(std::make_unique(this, organizer->modList())),
m_PluginListProxy(
- std::make_unique(this, organizer->pluginList()))
+ std::make_unique(this, organizer->pluginList())),
+ m_GameFeaturesProxy(
+ std::make_unique(this, pluginContainer->gameFeatures()))
{}
OrganizerProxy::~OrganizerProxy()
@@ -310,6 +313,11 @@ MOBase::IModList* OrganizerProxy::modList() const
return m_ModListProxy.get();
}
+MOBase::IGameFeatures* OrganizerProxy::gameFeatures() const
+{
+ return m_GameFeaturesProxy.get();
+}
+
MOBase::IProfile* OrganizerProxy::profile() const
{
return m_Proxied->currentProfile();
diff --git a/src/organizerproxy.h b/src/organizerproxy.h
index 751464328..fbae7cde6 100644
--- a/src/organizerproxy.h
+++ b/src/organizerproxy.h
@@ -8,6 +8,7 @@
#include "organizercore.h"
+class GameFeaturesProxy;
class PluginContainer;
class DownloadManagerProxy;
class ModListProxy;
@@ -59,10 +60,12 @@ class OrganizerProxy : public MOBase::IOrganizer
const std::function& filter) const override;
virtual std::shared_ptr virtualFileTree() const override;
- virtual MOBase::IDownloadManager* downloadManager() const;
- virtual MOBase::IPluginList* pluginList() const;
- virtual MOBase::IModList* modList() const;
+ virtual MOBase::IDownloadManager* downloadManager() const override;
+ virtual MOBase::IPluginList* pluginList() const override;
+ virtual MOBase::IModList* modList() const override;
virtual MOBase::IProfile* profile() const override;
+ virtual MOBase::IGameFeatures* gameFeatures() const override;
+
virtual HANDLE startApplication(const QString& executable,
const QStringList& args = QStringList(),
const QString& cwd = "", const QString& profile = "",
@@ -151,6 +154,7 @@ class OrganizerProxy : public MOBase::IOrganizer
std::unique_ptr m_DownloadManagerProxy;
std::unique_ptr m_ModListProxy;
std::unique_ptr m_PluginListProxy;
+ std::unique_ptr m_GameFeaturesProxy;
};
#endif // ORGANIZERPROXY_H
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index 157d33995..c73d45666 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -320,7 +320,9 @@ void PluginRequirements::requiredFor(std::vector& required,
// PluginContainer
PluginContainer::PluginContainer(OrganizerCore* organizer)
- : m_Organizer(organizer), m_UserInterface(nullptr), m_PreviewGenerator(*this)
+ : m_Organizer(organizer), m_UserInterface(nullptr),
+ m_GameFeatures(std::make_unique(organizer, this)),
+ m_PreviewGenerator(*this)
{}
PluginContainer::~PluginContainer()
@@ -625,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(m_Organizer->managedGame());
+ return m_Organizer ? const_cast(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& plugins) const
{
// setUserInterface()
diff --git a/src/plugincontainer.h b/src/plugincontainer.h
index d5ceca723..4854954d3 100644
--- a/src/plugincontainer.h
+++ b/src/plugincontainer.h
@@ -24,6 +24,8 @@ class IUserInterface;
#include
#include
+#include "game_features.h"
+
class OrganizerProxy;
/**
@@ -279,7 +281,7 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose
/**
* @return the IPlugin interface to the currently managed game.
*/
- MOBase::IPlugin* managedGame() const;
+ MOBase::IPluginGame* managedGame() const;
/**
* @brief Check if the given plugin is enabled.
@@ -337,10 +339,15 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose
*/
QString topImplementedInterface(MOBase::IPlugin* plugin) const;
+ /**
+ * @return the game features.
+ */
+ GameFeatures& gameFeatures() const { return *m_GameFeatures; }
+
/**
* @return the preview generator.
*/
- const PreviewGenerator& previewGenerator() const;
+ const PreviewGenerator& previewGenerator() const { return m_PreviewGenerator; }
/**
* @return the list of plugin file names, including proxied plugins.
@@ -476,6 +483,9 @@ class PluginContainer : public QObject, public MOBase::IPluginDiagnose
// Main user interface, can be null until MW has been initialized.
IUserInterface* m_UserInterface;
+ // Game features
+ std::unique_ptr m_GameFeatures;
+
PluginMap m_Plugins;
// This maps allow access to IPlugin* from name or diagnose/mapper object.
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index b1fd97579..748ffbada 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();
+ auto gamePlugins = m_Organizer.gameFeatures().gameFeature();
const bool lightPluginsAreSupported =
gamePlugins ? gamePlugins->lightPluginsAreSupported() : false;
const bool overridePluginsAreSupported =
@@ -673,7 +673,7 @@ void PluginList::writeLockedOrder(const QString& fileName) const
void PluginList::saveTo(const QString& lockedOrderFileName) const
{
- GamePlugins* gamePlugins = m_GamePlugin->feature();
+ auto gamePlugins = m_Organizer.gameFeatures().gameFeature();
if (gamePlugins) {
gamePlugins->writePluginLists(m_Organizer.managedGameOrganizer()->pluginList());
}
@@ -1085,7 +1085,7 @@ void PluginList::generatePluginIndexes()
int numESLs = 0;
int numSkipped = 0;
- GamePlugins* gamePlugins = m_GamePlugin->feature();
+ auto gamePlugins = m_Organizer.gameFeatures().gameFeature();
const bool lightPluginsSupported =
gamePlugins ? gamePlugins->lightPluginsAreSupported() : false;
const bool overridePluginsSupported =
@@ -1358,8 +1358,8 @@ QVariant PluginList::tooltipData(const QModelIndex& modelIndex) const
}
if (esp.forceDisabled) {
- if (m_GamePlugin->feature() && esp.hasLightExtension &&
- !m_GamePlugin->feature()->lightPluginsAreSupported()) {
+ auto feature = m_Organizer.gameFeatures().gameFeature();
+ if (feature && esp.hasLightExtension && feature->lightPluginsAreSupported()) {
toolTip += "
" + tr("Light plugins (ESL) are not supported by this game.");
} else {
toolTip += "
" + tr("This game does not currently permit custom plugin "
diff --git a/src/profile.cpp b/src/profile.cpp
index d4299a7b0..a0c5a2d4b 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see .
#include "profile.h"
#include "filesystemutilities.h"
+#include "game_features.h"
#include "modinfo.h"
#include "modinfoforeign.h"
#include "registry.h"
@@ -73,9 +74,9 @@ void Profile::touchFile(QString fileName)
}
Profile::Profile(const QString& name, IPluginGame const* gamePlugin,
- bool useDefaultSettings)
+ GameFeatures const& gameFeatures, bool useDefaultSettings)
: m_ModListWriter(std::bind(&Profile::doWriteModlist, this)),
- m_GamePlugin(gamePlugin)
+ m_GamePlugin(gamePlugin), m_GameFeatures(gameFeatures)
{
QString profilesDir = Settings::instance().paths().profiles();
QDir profileBase(profilesDir);
@@ -114,8 +115,9 @@ Profile::Profile(const QString& name, IPluginGame const* gamePlugin,
refreshModStatus();
}
-Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin)
- : m_Directory(directory), m_GamePlugin(gamePlugin),
+Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin,
+ GameFeatures const& gameFeatures)
+ : m_Directory(directory), m_GamePlugin(gamePlugin), m_GameFeatures(gameFeatures),
m_ModListWriter(std::bind(&Profile::doWriteModlist, this))
{
assert(gamePlugin != nullptr);
@@ -138,7 +140,7 @@ Profile::Profile(const QDir& directory, IPluginGame const* gamePlugin)
Profile::Profile(const Profile& reference)
: m_Directory(reference.m_Directory),
m_ModListWriter(std::bind(&Profile::doWriteModlist, this)),
- m_GamePlugin(reference.m_GamePlugin)
+ m_GamePlugin(reference.m_GamePlugin), m_GameFeatures(reference.m_GameFeatures)
{
m_Settings =
@@ -191,9 +193,9 @@ void Profile::findProfileSettings()
}
if (setting("", "AutomaticArchiveInvalidation") == QVariant()) {
- BSAInvalidation* invalidation = m_GamePlugin->feature();
- DataArchives* dataArchives = m_GamePlugin->feature();
- bool found = false;
+ auto invalidation = m_GameFeatures.gameFeature();
+ auto dataArchives = m_GameFeatures.gameFeature();
+ bool found = false;
if ((invalidation != nullptr) && (dataArchives != nullptr)) {
for (const QString& archive : dataArchives->archives(this)) {
if (invalidation->isInvalidationBSA(archive)) {
@@ -731,7 +733,7 @@ Profile* Profile::createPtrFrom(const QString& name, const Profile& reference,
{
QString profileDirectory = Settings::instance().paths().profiles() + "/" + name;
reference.copyFilesTo(profileDirectory);
- return new Profile(QDir(profileDirectory), gamePlugin);
+ return new Profile(QDir(profileDirectory), gamePlugin, reference.m_GameFeatures);
}
void Profile::copyFilesTo(QString& target) const
@@ -811,8 +813,8 @@ void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString& tweakedIni) const
bool Profile::invalidationActive(bool* supported) const
{
- BSAInvalidation* invalidation = m_GamePlugin->feature();
- DataArchives* dataArchives = m_GamePlugin->feature();
+ auto invalidation = m_GameFeatures.gameFeature();
+ auto dataArchives = m_GameFeatures.gameFeature();
if (supported != nullptr) {
*supported = ((invalidation != nullptr) && (dataArchives != nullptr));
@@ -825,7 +827,7 @@ bool Profile::invalidationActive(bool* supported) const
void Profile::deactivateInvalidation()
{
- BSAInvalidation* invalidation = m_GamePlugin->feature();
+ auto invalidation = m_GameFeatures.gameFeature();
if (invalidation != nullptr) {
invalidation->deactivate(this);
@@ -836,7 +838,7 @@ void Profile::deactivateInvalidation()
void Profile::activateInvalidation()
{
- BSAInvalidation* invalidation = m_GamePlugin->feature();
+ auto invalidation = m_GameFeatures.gameFeature();
if (invalidation != nullptr) {
invalidation->activate(this);
diff --git a/src/profile.h b/src/profile.h
index 0adb4d0fe..fcce75ee7 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -43,6 +43,8 @@ namespace MOBase
class IPluginGame;
}
+class GameFeatures;
+
/**
* @brief represents a profile
**/
@@ -70,7 +72,7 @@ class Profile : public QObject, public MOBase::IProfile
* @param filter save game filter. Defaults to <no filter>.
**/
Profile(const QString& name, MOBase::IPluginGame const* gamePlugin,
- bool useDefaultSettings);
+ GameFeatures const& features, bool useDefaultSettings);
/**
* @brief constructor
@@ -80,7 +82,8 @@ class Profile : public QObject, public MOBase::IProfile
*so technically, invoking this should always produce a working profile
* @param directory directory to read the profile from
**/
- Profile(const QDir& directory, MOBase::IPluginGame const* gamePlugin);
+ Profile(const QDir& directory, MOBase::IPluginGame const* gamePlugin,
+ GameFeatures const& features);
Profile(const Profile& reference);
@@ -398,6 +401,7 @@ protected slots:
QSettings* m_Settings;
+ const GameFeatures& m_GameFeatures;
const MOBase::IPluginGame* m_GamePlugin;
std::vector m_ModStatus;
diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp
index f8010adaa..24eddc204 100644
--- a/src/profilesdialog.cpp
+++ b/src/profilesdialog.cpp
@@ -22,6 +22,7 @@ along with Mod Organizer. If not, see .
#include "bsainvalidation.h"
#include "filesystemutilities.h"
+#include "game_features.h"
#include "iplugingame.h"
#include "localsavegames.h"
#include "organizercore.h"
@@ -52,7 +53,8 @@ Q_DECLARE_METATYPE(Profile::Ptr)
ProfilesDialog::ProfilesDialog(const QString& profileName, OrganizerCore& organizer,
QWidget* parent)
: TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog),
- m_FailState(false), m_Game(organizer.managedGame()), m_ActiveProfileName("")
+ m_GameFeatures(organizer.gameFeatures()), m_FailState(false),
+ m_Game(organizer.managedGame()), m_ActiveProfileName("")
{
ui->setupUi(this);
@@ -70,15 +72,14 @@ ProfilesDialog::ProfilesDialog(const QString& profileName, OrganizerCore& organi
}
}
- BSAInvalidation* invalidation = m_Game->feature();
-
+ auto invalidation = m_GameFeatures.gameFeature();
if (invalidation == nullptr) {
ui->invalidationBox->setToolTip(
tr("Archive invalidation isn't required for this game."));
ui->invalidationBox->setEnabled(false);
}
- if (!m_Game->feature()) {
+ if (!m_GameFeatures.gameFeature()) {
ui->localSavesBox->setToolTip(
tr("This game does not support profile-specific game saves."));
ui->localSavesBox->setEnabled(false);
@@ -149,8 +150,8 @@ QListWidgetItem* ProfilesDialog::addItem(const QString& name)
QListWidgetItem* newItem =
new QListWidgetItem(profileDir.dirName(), ui->profilesList);
try {
- newItem->setData(Qt::UserRole, QVariant::fromValue(
- Profile::Ptr(new Profile(profileDir, m_Game))));
+ newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(
+ profileDir, m_Game, m_GameFeatures))));
m_FailState = false;
} catch (const std::exception& e) {
reportError(tr("failed to create profile: %1").arg(e.what()));
@@ -161,7 +162,8 @@ QListWidgetItem* ProfilesDialog::addItem(const QString& name)
void ProfilesDialog::createProfile(const QString& name, bool useDefaultSettings)
{
try {
- auto profile = Profile::Ptr(new Profile(name, m_Game, useDefaultSettings));
+ auto profile =
+ Profile::Ptr(new Profile(name, m_Game, m_GameFeatures, useDefaultSettings));
QListWidgetItem* newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(profile));
ui->profilesList->addItem(newItem);
diff --git a/src/profilesdialog.h b/src/profilesdialog.h
index dc8329a26..1a2a36e24 100644
--- a/src/profilesdialog.h
+++ b/src/profilesdialog.h
@@ -20,13 +20,12 @@ along with Mod Organizer. If not, see .
#ifndef PROFILESDIALOG_H
#define PROFILESDIALOG_H
+#include
+
#include "tutorabledialog.h"
-class Profile;
-class OrganizerCore;
class QListWidget;
class QListWidgetItem;
-#include
class QString;
namespace Ui
@@ -39,6 +38,10 @@ namespace MOBase
class IPluginGame;
}
+class GameFeatures;
+class Profile;
+class OrganizerCore;
+
/**
* @brief Dialog that can be used to create/delete/modify profiles
**/
@@ -126,6 +129,7 @@ private slots:
private:
Ui::ProfilesDialog* ui;
+ GameFeatures& m_GameFeatures;
QListWidget* m_ProfilesList;
bool m_FailState;
MOBase::IPluginGame const* m_Game;
diff --git a/src/savestab.cpp b/src/savestab.cpp
index 813fea052..2cbd17de9 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();
+ auto info = m_core.gameFeatures().gameFeature();
if (info != nullptr) {
m_CurrentSaveView = info->getSaveGameWidget(m_window);
@@ -199,7 +198,7 @@ void SavesTab::refreshSaveList()
void SavesTab::deleteSavegame()
{
- SaveGameInfo const* info = m_core.managedGame()->feature();
+ auto info = m_core.gameFeatures().gameFeature();
QString savesMsgLabel;
QStringList deleteFiles;
@@ -246,7 +245,7 @@ void SavesTab::onContextMenu(const QPoint& pos)
QMenu menu;
- SaveGameInfo const* info = this->m_core.managedGame()->feature();
+ auto info = m_core.gameFeatures().gameFeature();
if (info != nullptr) {
QAction* action = menu.addAction(tr("Fix enabled mods..."));
action->setEnabled(false);
@@ -304,7 +303,7 @@ void SavesTab::fixMods(SaveGameInfo::MissingAssets const& missingAssets)
void SavesTab::openInExplorer()
{
- const SaveGameInfo* info = m_core.managedGame()->feature();
+ auto info = m_core.gameFeatures().gameFeature();
const auto sel = ui.list->selectionModel()->selectedRows();
if (sel.empty()) {
diff --git a/src/savestab.h b/src/savestab.h
index a903e4fb5..6a32daf9f 100644
--- a/src/savestab.h
+++ b/src/savestab.h
@@ -58,7 +58,7 @@ class SavesTab : public QObject
void onContextMenu(const QPoint& pos);
void deleteSavegame();
void saveSelectionChanged(QTreeWidgetItem* newItem);
- void fixMods(SaveGameInfo::MissingAssets const& missingAssets);
+ void fixMods(MOBase::SaveGameInfo::MissingAssets const& missingAssets);
void refreshSavesIfOpen();
void openInExplorer();
};