From 74311a54cf2f423a160ce0999bd5ad7e5c62f243 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Fri, 28 May 2021 15:03:14 +0100 Subject: [PATCH 1/2] NOISSUE Support ATLauncher optional mods --- .../atlauncher/ATLPackInstallTask.cpp | 21 ++- .../atlauncher/ATLPackInstallTask.h | 5 + .../atlauncher/ATLPackManifest.cpp | 3 + .../modplatform/atlauncher/ATLPackManifest.h | 3 + application/CMakeLists.txt | 5 + .../atlauncher/AtlOptionalModDialog.cpp | 138 ++++++++++++++++++ .../atlauncher/AtlOptionalModDialog.h | 60 ++++++++ .../atlauncher/AtlOptionalModDialog.ui | 65 +++++++++ .../pages/modplatform/atlauncher/AtlPage.cpp | 7 + .../pages/modplatform/atlauncher/AtlPage.h | 1 + 10 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp create mode 100644 application/pages/modplatform/atlauncher/AtlOptionalModDialog.h create mode 100644 application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui diff --git a/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp b/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp index 89c4dfd3..89829c05 100644 --- a/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -457,16 +457,31 @@ void PackInstallTask::extractConfigs() void PackInstallTask::downloadMods() { qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId(); + + QVector optionalMods; + for (const auto& mod : m_version.mods) { + if (mod.optional) { + optionalMods.push_back(mod); + } + } + + // Select optional mods, if pack contains any + QVector selectedMods; + if (!optionalMods.isEmpty()) { + setStatus(tr("Selecting optional mods...")); + selectedMods = m_support->chooseOptionalMods(optionalMods); + } + setStatus(tr("Downloading mods...")); jarmods.clear(); jobPtr.reset(new NetJob(tr("Mod download"))); for(const auto& mod : m_version.mods) { // skip non-client mods - if (!mod.client) continue; + if(!mod.client) continue; - // skip optional mods for now - if(mod.optional) continue; + // skip optional mods that were not selected + if(mod.optional && !selectedMods.contains(mod.name)) continue; QString url; switch(mod.download) { diff --git a/api/logic/modplatform/atlauncher/ATLPackInstallTask.h b/api/logic/modplatform/atlauncher/ATLPackInstallTask.h index 3647e471..8233c376 100644 --- a/api/logic/modplatform/atlauncher/ATLPackInstallTask.h +++ b/api/logic/modplatform/atlauncher/ATLPackInstallTask.h @@ -18,6 +18,11 @@ namespace ATLauncher { class MULTIMC_LOGIC_EXPORT UserInteractionSupport { public: + /** + * Requests a user interaction to select which optional mods should be installed. + */ + virtual QVector chooseOptionalMods(QVector mods) = 0; + /** * Requests a user interaction to select a component version from a given version list * and constrained to a given Minecraft version. diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp index 57cc52b6..149cb9c1 100644 --- a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp +++ b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp @@ -143,7 +143,10 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { p.decompFile = Json::requireString(obj, "decompFile"); } + p.description = Json::ensureString(obj, QString("description"), ""); p.optional = Json::ensureBoolean(obj, QString("optional"), false); + p.recommended = Json::ensureBoolean(obj, QString("recommended"), false); + p.selected = Json::ensureBoolean(obj, QString("selected"), false); p.client = Json::ensureBoolean(obj, QString("client"), false); } diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.h b/api/logic/modplatform/atlauncher/ATLPackManifest.h index 937106a5..48e1d344 100644 --- a/api/logic/modplatform/atlauncher/ATLPackManifest.h +++ b/api/logic/modplatform/atlauncher/ATLPackManifest.h @@ -86,7 +86,10 @@ struct VersionMod QString decompType_raw; QString decompFile; + QString description; bool optional; + bool recommended; + bool selected; bool client; }; diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index c5be22d0..ab2b9960 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -129,6 +129,8 @@ SET(MULTIMC_SOURCES pages/modplatform/atlauncher/AtlFilterModel.h pages/modplatform/atlauncher/AtlListModel.cpp pages/modplatform/atlauncher/AtlListModel.h + pages/modplatform/atlauncher/AtlOptionalModDialog.cpp + pages/modplatform/atlauncher/AtlOptionalModDialog.h pages/modplatform/atlauncher/AtlPage.cpp pages/modplatform/atlauncher/AtlPage.h @@ -278,6 +280,9 @@ SET(MULTIMC_UIS pages/modplatform/technic/TechnicPage.ui pages/modplatform/ImportPage.ui + # Platform Dialogs + pages/modplatform/atlauncher/AtlOptionalModDialog.ui + # Dialogs dialogs/CopyInstanceDialog.ui dialogs/NewComponentDialog.ui diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp new file mode 100644 index 00000000..129d9fed --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -0,0 +1,138 @@ +#include "AtlOptionalModDialog.h" +#include "ui_AtlOptionalModDialog.h" + +AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector mods) + : QAbstractListModel(parent), m_mods(mods) { + + for (const auto& mod : mods) { + m_selection[mod.name] = mod.selected; + } +} + +QVector AtlOptionalModListModel::getResult() { + QVector result; + + for (const auto& mod : m_mods) { + if (m_selection[mod.name]) { + result.push_back(mod.name); + } + } + + return result; +} + +int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const { + return m_mods.size(); +} + +int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const { + // Enabled, Name, Description + return 3; +} + +QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const { + auto row = index.row(); + auto mod = m_mods.at(row); + + if (role == Qt::DisplayRole) { + if (index.column() == NameColumn) { + return mod.name; + } + if (index.column() == DescriptionColumn) { + return mod.description; + } + } + else if (role == Qt::ToolTipRole) { + if (index.column() == DescriptionColumn) { + return mod.description; + } + } + else if (role == Qt::CheckStateRole) { + if (index.column() == EnabledColumn) { + return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked; + } + } + + return QVariant(); +} + +bool AtlOptionalModListModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role == Qt::CheckStateRole) { + auto row = index.row(); + auto mod = m_mods.at(row); + + // toggle the state + m_selection[mod.name] = !m_selection[mod.name]; + + emit dataChanged(AtlOptionalModListModel::index(index.row(), EnabledColumn), + AtlOptionalModListModel::index(index.row(), EnabledColumn)); + return true; + } + + return false; +} + +QVariant AtlOptionalModListModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + switch (section) { + case EnabledColumn: + return QString(); + case NameColumn: + return QString("Name"); + case DescriptionColumn: + return QString("Description"); + } + } + + return QVariant(); +} + +Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const { + auto flags = QAbstractListModel::flags(index); + if (index.isValid() && index.column() == EnabledColumn) { + flags |= Qt::ItemIsUserCheckable; + } + return flags; +} + +void AtlOptionalModListModel::selectRecommended() { + for (const auto& mod : m_mods) { + m_selection[mod.name] = mod.recommended; + } + + emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), + AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); +} + +void AtlOptionalModListModel::clearAll() { + for (const auto& mod : m_mods) { + m_selection[mod.name] = false; + } + + emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), + AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); +} + + +AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector mods) + : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { + ui->setupUi(this); + + listModel = new AtlOptionalModListModel(this, mods); + ui->treeView->setModel(listModel); + + ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->treeView->header()->setSectionResizeMode( + AtlOptionalModListModel::NameColumn, QHeaderView::ResizeToContents); + ui->treeView->header()->setSectionResizeMode( + AtlOptionalModListModel::DescriptionColumn, QHeaderView::Stretch); + + connect(ui->selectRecommendedButton, &QPushButton::pressed, + listModel, &AtlOptionalModListModel::selectRecommended); + connect(ui->clearAllButton, &QPushButton::pressed, + listModel, &AtlOptionalModListModel::clearAll); +} + +AtlOptionalModDialog::~AtlOptionalModDialog() { + delete ui; +} diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h new file mode 100644 index 00000000..8b0dbdb6 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include "modplatform/atlauncher/ATLPackIndex.h" + +namespace Ui { +class AtlOptionalModDialog; +} + +class AtlOptionalModListModel : public QAbstractListModel { + Q_OBJECT + +public: + enum Columns + { + EnabledColumn = 0, + NameColumn, + DescriptionColumn, + }; + + AtlOptionalModListModel(QWidget *parent, QVector mods); + + QVector getResult(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + +public slots: + void selectRecommended(); + void clearAll(); + +private: + QVector m_mods; + QMap m_selection; +}; + +class AtlOptionalModDialog : public QDialog { + Q_OBJECT + +public: + AtlOptionalModDialog(QWidget *parent, QVector mods); + ~AtlOptionalModDialog() override; + + QVector getResult() { + return listModel->getResult(); + } + +private: + Ui::AtlOptionalModDialog *ui; + + AtlOptionalModListModel *listModel; +}; diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui new file mode 100644 index 00000000..5d3193a4 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui @@ -0,0 +1,65 @@ + + + AtlOptionalModDialog + + + + 0 + 0 + 550 + 310 + + + + Select Mods To Install + + + + + + Install + + + true + + + + + + + Select Recommended + + + + + + + false + + + Use Share Code + + + + + + + Clear All + + + + + + + + + + + ModListView + QTreeView +
widgets/ModListView.h
+
+
+ + +
diff --git a/application/pages/modplatform/atlauncher/AtlPage.cpp b/application/pages/modplatform/atlauncher/AtlPage.cpp index 3c19804f..9fdf111f 100644 --- a/application/pages/modplatform/atlauncher/AtlPage.cpp +++ b/application/pages/modplatform/atlauncher/AtlPage.cpp @@ -2,6 +2,7 @@ #include "ui_AtlPage.h" #include "dialogs/NewInstanceDialog.h" +#include "AtlOptionalModDialog.h" #include #include #include @@ -131,6 +132,12 @@ void AtlPage::onVersionSelectionChanged(QString data) suggestCurrent(); } +QVector AtlPage::chooseOptionalMods(QVector mods) { + AtlOptionalModDialog optionalModDialog(this, mods); + optionalModDialog.exec(); + return optionalModDialog.getResult(); +} + QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) { VersionSelectDialog vselect(vlist.get(), "Choose Version", MMC->activeWindow(), false); if (minecraftVersion != Q_NULLPTR) { diff --git a/application/pages/modplatform/atlauncher/AtlPage.h b/application/pages/modplatform/atlauncher/AtlPage.h index 18f8b1c6..932ec6a6 100644 --- a/application/pages/modplatform/atlauncher/AtlPage.h +++ b/application/pages/modplatform/atlauncher/AtlPage.h @@ -63,6 +63,7 @@ private: void suggestCurrent(); QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; + QVector chooseOptionalMods(QVector mods) override; private slots: void triggerSearch(); From 4ba0c9c2986d9fb133db923d2da60de9272ccc0a Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Fri, 28 May 2021 23:10:02 +0100 Subject: [PATCH 2/2] NOISSUE Support mod grouping and dependencies --- .../atlauncher/ATLPackManifest.cpp | 13 +++ .../modplatform/atlauncher/ATLPackManifest.h | 8 ++ .../atlauncher/AtlOptionalModDialog.cpp | 83 +++++++++++++++++-- .../atlauncher/AtlOptionalModDialog.h | 6 ++ 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp index 149cb9c1..f28fd35c 100644 --- a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp +++ b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp @@ -147,7 +147,20 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { p.optional = Json::ensureBoolean(obj, QString("optional"), false); p.recommended = Json::ensureBoolean(obj, QString("recommended"), false); p.selected = Json::ensureBoolean(obj, QString("selected"), false); + p.hidden = Json::ensureBoolean(obj, QString("hidden"), false); + p.library = Json::ensureBoolean(obj, QString("library"), false); + p.group = Json::ensureString(obj, QString("group"), ""); + if(obj.contains("depends")) { + auto dependsArr = Json::requireArray(obj, "depends"); + for (const auto depends : dependsArr) { + p.depends.append(Json::requireString(depends)); + } + } + p.client = Json::ensureBoolean(obj, QString("client"), false); + + // computed + p.effectively_hidden = p.hidden || p.library; } void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.h b/api/logic/modplatform/atlauncher/ATLPackManifest.h index 48e1d344..376587b0 100644 --- a/api/logic/modplatform/atlauncher/ATLPackManifest.h +++ b/api/logic/modplatform/atlauncher/ATLPackManifest.h @@ -90,7 +90,15 @@ struct VersionMod bool optional; bool recommended; bool selected; + bool hidden; + bool library; + QString group; + QVector depends; + bool client; + + // computed + bool effectively_hidden; }; struct PackVersion diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 129d9fed..cffe5d55 100644 --- a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -4,8 +4,16 @@ AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector mods) : QAbstractListModel(parent), m_mods(mods) { - for (const auto& mod : mods) { - m_selection[mod.name] = mod.selected; + // fill mod index + for (int i = 0; i < m_mods.size(); i++) { + auto mod = m_mods.at(i); + m_index[mod.name] = i; + } + // set initial state + for (int i = 0; i < m_mods.size(); i++) { + auto mod = m_mods.at(i); + m_selection[mod.name] = false; + setMod(mod, i, mod.selected, false); } } @@ -61,11 +69,7 @@ bool AtlOptionalModListModel::setData(const QModelIndex &index, const QVariant & auto row = index.row(); auto mod = m_mods.at(row); - // toggle the state - m_selection[mod.name] = !m_selection[mod.name]; - - emit dataChanged(AtlOptionalModListModel::index(index.row(), EnabledColumn), - AtlOptionalModListModel::index(index.row(), EnabledColumn)); + toggleMod(mod, row); return true; } @@ -113,6 +117,71 @@ void AtlOptionalModListModel::clearAll() { AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); } +void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) { + setMod(mod, index, !m_selection[mod.name]); +} + +void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) { + if (m_selection[mod.name] == enable) return; + + m_selection[mod.name] = enable; + + // disable other mods in the group, if applicable + if (enable && !mod.group.isEmpty()) { + for (int i = 0; i < m_mods.size(); i++) { + if (index == i) continue; + auto other = m_mods.at(i); + + if (mod.group == other.group) { + setMod(other, i, false, shouldEmit); + } + } + } + + for (const auto& dependencyName : mod.depends) { + auto dependencyIndex = m_index[dependencyName]; + auto dependencyMod = m_mods.at(dependencyIndex); + + // enable/disable dependencies + if (enable) { + setMod(dependencyMod, dependencyIndex, true, shouldEmit); + } + + // if the dependency is 'effectively hidden', then track which mods + // depend on it - so we can efficiently disable it when no more dependents + // depend on it. + auto dependants = m_dependants[dependencyName]; + + if (enable) { + dependants.append(mod.name); + } + else { + dependants.removeAll(mod.name); + + // if there are no longer any dependents, let's disable the mod + if (dependencyMod.effectively_hidden && dependants.isEmpty()) { + setMod(dependencyMod, dependencyIndex, false, shouldEmit); + } + } + } + + // disable mods that depend on this one, if disabling + if (!enable) { + auto dependants = m_dependants[mod.name]; + for (const auto& dependencyName : dependants) { + auto dependencyIndex = m_index[dependencyName]; + auto dependencyMod = m_mods.at(dependencyIndex); + + setMod(dependencyMod, dependencyIndex, false, shouldEmit); + } + } + + if (shouldEmit) { + emit dataChanged(AtlOptionalModListModel::index(index, EnabledColumn), + AtlOptionalModListModel::index(index, EnabledColumn)); + } +} + AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 8b0dbdb6..a1df43f6 100644 --- a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -37,9 +37,15 @@ public slots: void selectRecommended(); void clearAll(); +private: + void toggleMod(ATLauncher::VersionMod mod, int index); + void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true); + private: QVector m_mods; QMap m_selection; + QMap m_index; + QMap> m_dependants; }; class AtlOptionalModDialog : public QDialog {