From 284e536e8144b3fc7ff4842c39e0d7231f8e4b93 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 28 Nov 2023 12:30:13 +0000 Subject: [PATCH 01/22] Data pack management Signed-off-by: TheKodeToad --- launcher/Application.cpp | 5 + launcher/CMakeLists.txt | 7 + launcher/minecraft/mod/DataPack.cpp | 47 +++++ launcher/minecraft/mod/DataPack.h | 17 ++ .../minecraft/mod/DataPackFolderModel.cpp | 191 ++++++++++++++++++ launcher/minecraft/mod/DataPackFolderModel.h | 62 ++++++ .../mod/tasks/LocalDataPackParseTask.cpp | 121 +++++++++++ .../mod/tasks/LocalDataPackParseTask.h | 4 + launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/flame/FlameAPI.h | 1 + launcher/modplatform/modrinth/ModrinthAPI.h | 3 + .../ui/dialogs/ResourceDownloadDialog.cpp | 23 ++- launcher/ui/dialogs/ResourceDownloadDialog.h | 18 ++ launcher/ui/pages/instance/DataPackPage.cpp | 82 ++++++++ launcher/ui/pages/instance/DataPackPage.h | 39 ++++ .../ui/pages/instance/ExternalResourcesPage.h | 1 + launcher/ui/pages/instance/WorldListPage.cpp | 34 +++- launcher/ui/pages/instance/WorldListPage.h | 6 +- launcher/ui/pages/instance/WorldListPage.ui | 8 +- .../ui/pages/modplatform/DataPackModel.cpp | 48 +++++ launcher/ui/pages/modplatform/DataPackModel.h | 44 ++++ .../ui/pages/modplatform/DataPackPage.cpp | 48 +++++ launcher/ui/pages/modplatform/DataPackPage.h | 51 +++++ .../modrinth/ModrinthResourceModels.cpp | 23 +++ .../modrinth/ModrinthResourceModels.h | 19 ++ .../modrinth/ModrinthResourcePages.cpp | 22 ++ .../modrinth/ModrinthResourcePages.h | 24 +++ launcher/ui/widgets/InfoFrame.cpp | 6 + launcher/ui/widgets/InfoFrame.h | 2 + 29 files changed, 943 insertions(+), 15 deletions(-) create mode 100644 launcher/minecraft/mod/DataPackFolderModel.cpp create mode 100644 launcher/minecraft/mod/DataPackFolderModel.h create mode 100644 launcher/ui/pages/instance/DataPackPage.cpp create mode 100644 launcher/ui/pages/instance/DataPackPage.h create mode 100644 launcher/ui/pages/modplatform/DataPackModel.cpp create mode 100644 launcher/ui/pages/modplatform/DataPackModel.h create mode 100644 launcher/ui/pages/modplatform/DataPackPage.cpp create mode 100644 launcher/ui/pages/modplatform/DataPackPage.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index be252f1c5..2c3189a90 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -683,6 +683,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("RPDownloadGeometry", ""); m_settings->registerSetting("TPDownloadGeometry", ""); m_settings->registerSetting("ShaderDownloadGeometry", ""); + m_settings->registerSetting("DataPackDownloadGeometry", ""); + + // data pack window + // in future, more pages may be added - so this name is chosen to avoid needing migration + m_settings->registerSetting("WorldManagementGeometry", ""); // HACK: This code feels so stupid is there a less stupid way of doing this? { diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 99acf8fc5..36451ab8a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -333,6 +333,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ResourceFolderModel.cpp minecraft/mod/DataPack.h minecraft/mod/DataPack.cpp + minecraft/mod/DataPackFolderModel.h + minecraft/mod/DataPackFolderModel.cpp minecraft/mod/ResourcePack.h minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePackFolderModel.h @@ -861,6 +863,8 @@ SET(LAUNCHER_SOURCES ui/pages/instance/VersionPage.h ui/pages/instance/ManagedPackPage.cpp ui/pages/instance/ManagedPackPage.h + ui/pages/instance/DataPackPage.h + ui/pages/instance/DataPackPage.cpp ui/pages/instance/TexturePackPage.h ui/pages/instance/TexturePackPage.cpp ui/pages/instance/ResourcePackPage.h @@ -930,6 +934,9 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ShaderPackPage.cpp ui/pages/modplatform/ShaderPackModel.cpp + ui/pages/modplatform/DataPackPage.cpp + ui/pages/modplatform/DataPackModel.cpp + ui/pages/modplatform/atlauncher/AtlFilterModel.cpp ui/pages/modplatform/atlauncher/AtlFilterModel.h ui/pages/modplatform/atlauncher/AtlListModel.cpp diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index fc2d3f68b..8c7d1b086 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -25,7 +25,9 @@ #include #include +#include "MTPixmapCache.h" #include "Version.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" // Values taken from: // https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22 @@ -56,6 +58,51 @@ void DataPack::setDescription(QString new_description) m_description = new_description; } +void DataPack::setImage(QImage new_image) const +{ + QMutexLocker locker(&m_data_lock); + + Q_ASSERT(!new_image.isNull()); + + if (m_pack_image_cache_key.key.isValid()) + PixmapCache::instance().remove(m_pack_image_cache_key.key); + + // scale the image to avoid flooding the pixmapcache + auto pixmap = + QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); + + m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap); + m_pack_image_cache_key.was_ever_used = true; + + // This can happen if the pixmap is too big to fit in the cache :c + if (!m_pack_image_cache_key.key.isValid()) { + qWarning() << "Could not insert a image cache entry! Ignoring it."; + m_pack_image_cache_key.was_ever_used = false; + } +} + +QPixmap DataPack::image(QSize size, Qt::AspectRatioMode mode) const +{ + QPixmap cached_image; + if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) { + if (size.isNull()) + return cached_image; + return cached_image.scaled(size, mode, Qt::SmoothTransformation); + } + + // No valid image we can get + if (!m_pack_image_cache_key.was_ever_used) { + return {}; + } else { + qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading..."; + PixmapCache::markCacheMissByEviciton(); + } + + // Imaged got evicted from the cache. Re-process it and retry. + DataPackUtils::processPackPNG(*this); + return image(size); +} + std::pair DataPack::compatibleVersions() const { if (!s_pack_format_versions.contains(m_pack_format)) { diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index b3787b238..3eec4e0e0 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -24,6 +24,7 @@ #include "Resource.h" #include +#include class Version; @@ -48,12 +49,18 @@ class DataPack : public Resource { /** Gets the description of the data pack. */ [[nodiscard]] QString description() const { return m_description; } + /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; + /** Thread-safe. */ void setPackFormat(int new_format_id); /** Thread-safe. */ void setDescription(QString new_description); + /** Thread-safe. */ + void setImage(QImage new_image) const; + bool valid() const override; [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; @@ -70,4 +77,14 @@ class DataPack : public Resource { /** The data pack's description, as defined in the pack.mcmeta file. */ QString m_description; + + /** The data pack's image file cache key, for access in the QPixmapCache global instance. + * + * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true), + * so as to tell whether a cache entry is inexistent or if it was just evicted from the cache. + */ + struct { + QPixmapCache::Key key; + bool was_ever_used = false; + } mutable m_pack_image_cache_key; }; diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp new file mode 100644 index 000000000..9efb37294 --- /dev/null +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DataPackFolderModel.h" +#include +#include + +#include +#include + +#include "Application.h" +#include "Version.h" + +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" + +DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) +{ + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true }; +} + +QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const +{ + if (!validateIndex(index)) + return {}; + + int row = index.row(); + int column = index.column(); + + switch (role) { + case Qt::DisplayRole: + switch (column) { + case NameColumn: + return m_resources[row]->name(); + case PackFormatColumn: { + auto resource = at(row); + auto pack_format = resource->packFormat(); + if (pack_format == 0) + return tr("Unrecognized"); + + auto version_bounds = resource->compatibleVersions(); + if (version_bounds.first.toString().isEmpty()) + return QString::number(pack_format); + + return QString("%1 (%2 - %3)") + .arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString()); + } + case DateColumn: + return m_resources[row]->dateTimeChanged(); + + default: + return {}; + } + case Qt::DecorationRole: { + if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink())) + return APPLICATION->getThemedIcon("status-yellow"); + if (column == ImageColumn) { + return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding); + } + return {}; + } + case Qt::ToolTipRole: { + if (column == PackFormatColumn) { + //: The string being explained by this is in the format: ID (Lower version - Upper version) + return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); + } + if (column == NameColumn) { + if (at(row)->isSymLinkUnder(instDirPath())) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row)->fileinfo().canonicalFilePath()); + ; + } + if (at(row)->isMoreThanOneHardLink()) { + return m_resources[row]->internal_id() + + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + } + } + return m_resources[row]->internal_id(); + } + case Qt::SizeHintRole: + if (column == ImageColumn) { + return QSize(32, 32); + } + return {}; + case Qt::CheckStateRole: + switch (column) { + case ActiveColumn: + return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + default: + return {}; + } + default: + return {}; + } +} + +QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case ActiveColumn: + case NameColumn: + case PackFormatColumn: + case DateColumn: + case ImageColumn: + return columnNames().at(section); + default: + return {}; + } + + case Qt::ToolTipRole: + switch (section) { + case ActiveColumn: + return tr("Is the data pack enabled? (Only valid for ZIPs)"); + case NameColumn: + return tr("The name of the data pack."); + case PackFormatColumn: + //: The string being explained by this is in the format: ID (Lower version - Upper version) + return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); + case DateColumn: + return tr("The date and time this data pack was last changed (or added)."); + default: + return {}; + } + case Qt::SizeHintRole: + if (section == ImageColumn) { + return QSize(64, 0); + } + return {}; + default: + return {}; + } +} + +int DataPackFolderModel::columnCount(const QModelIndex& parent) const +{ + return parent.isValid() ? 0 : NUM_COLUMNS; +} + +Task* DataPackFolderModel::createUpdateTask() +{ + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared(entry); }); +} + +Task* DataPackFolderModel::createParseTask(Resource& resource) +{ + return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast(resource)); +} diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h new file mode 100644 index 000000000..1c2204af9 --- /dev/null +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "ResourceFolderModel.h" + +#include "DataPack.h" +#include "ResourcePack.h" + +class DataPackFolderModel : public ResourceFolderModel { + Q_OBJECT + public: + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + + explicit DataPackFolderModel(const QString& dir, BaseInstance* instance); + + virtual QString id() const override { return "datapacks"; } + + [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] int columnCount(const QModelIndex& parent) const override; + + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource&) override; + + RESOURCE_HELPERS(DataPack) +}; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 82f6b9df9..e5148e5be 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -81,6 +81,29 @@ bool processFolder(DataPack& pack, ProcessingLevel level) return true; // only need basic info already checked } + auto png_invalid = [&pack]() { + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.exists() && image_file_info.isFile()) { + QFile pack_png_file(image_file_info.filePath()); + if (!pack_png_file.open(QIODevice::ReadOnly)) + return png_invalid(); // can't open pack.png file + + auto data = pack_png_file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + pack_png_file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // pack.png does not exists or is not a valid file. + } + return true; // all tests passed } @@ -128,6 +151,32 @@ bool processZIP(DataPack& pack, ProcessingLevel level) return true; // only need basic info already checked } + auto png_invalid = [&pack]() { + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return true; // the png is optional + }; + + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return png_invalid(); + } + + auto data = file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + file.close(); + zip.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + zip.close(); + return png_invalid(); // could not set pack.mcmeta as current file. + } + zip.close(); return true; @@ -149,6 +198,78 @@ bool processMCMeta(DataPack& pack, QByteArray&& raw_data) return true; } +bool processPackPNG(const DataPack& pack, QByteArray&& raw_data) +{ + auto img = QImage::fromData(raw_data); + if (!img.isNull()) { + pack.setImage(img); + } else { + qWarning() << "Failed to parse pack.png."; + return false; + } + return true; +} + +bool processPackPNG(const DataPack& pack) +{ + auto png_invalid = [&pack]() { + qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png"; + return false; + }; + + switch (pack.type()) { + case ResourceType::FOLDER: { + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.exists() && image_file_info.isFile()) { + QFile pack_png_file(image_file_info.filePath()); + if (!pack_png_file.open(QIODevice::ReadOnly)) + return png_invalid(); // can't open pack.png file + + auto data = pack_png_file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + pack_png_file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // pack.png does not exists or is not a valid file. + } + return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 + } + case ResourceType::ZIPFILE: { + QuaZip zip(pack.fileinfo().filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return false; // can't open zip file + + QuaZipFile file(&zip); + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return png_invalid(); + } + + auto data = file.readAll(); + + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + + file.close(); + if (!pack_png_result) { + return png_invalid(); // pack.png invalid + } + } else { + return png_invalid(); // could not set pack.mcmeta as current file. + } + return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 + } + default: + qWarning() << "Invalid type for data pack parse task!"; + return false; + } +} + bool validate(QFileInfo file) { DataPack dp{ file }; diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h index 12fd8c82c..4a83437ca 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h @@ -38,6 +38,10 @@ bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processMCMeta(DataPack& pack, QByteArray&& raw_data); +bool processPackPNG(const DataPack& pack, QByteArray&& raw_data); + +/// processes ONLY the pack.png (rest of the pack may be invalid) +bool processPackPNG(const DataPack& pack); /** Checks whether a file is valid as a data pack or not. */ bool validate(QFileInfo file); diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 72294c399..8b3fd15f8 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -36,7 +36,7 @@ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) enum class ResourceProvider { MODRINTH, FLAME }; -enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; +enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, DATA_PACK }; enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index e22d8f0d8..449a1625a 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -37,6 +37,7 @@ class FlameAPI : public NetworkResourceAPI { case ModPlatform::ResourceType::MOD: return 6; case ModPlatform::ResourceType::RESOURCE_PACK: + case ModPlatform::ResourceType::DATA_PACK: return 12; case ModPlatform::ResourceType::SHADER_PACK: return 6552; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index d0f0811b2..d21d37d7e 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -61,6 +61,7 @@ class ModrinthAPI : public NetworkResourceAPI { { switch (type) { case ModPlatform::ResourceType::MOD: + case ModPlatform::ResourceType::DATA_PACK: return "mod"; case ModPlatform::ResourceType::RESOURCE_PACK: return "resourcepack"; @@ -81,6 +82,8 @@ class ModrinthAPI : public NetworkResourceAPI { facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); if (args.versions.has_value()) facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + if (args.type == ModPlatform::ResourceType::DATA_PACK) + facets_list.append("[\"categories:datapack\"]"); facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); return QString("[%1]").arg(facets_list.join(',')); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92c..53c740d94 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -57,7 +57,7 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::share { setObjectName(QStringLiteral("ResourceDownloadDialog")); - resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); + resize(static_cast(std::max(0.5 * parent->width(), 400.0)), static_cast(std::max(0.75 * parent->height(), 400.0))); setWindowIcon(APPLICATION->getThemedIcon("new")); @@ -356,4 +356,25 @@ QList ShaderPackDownloadDialog::getPages() return pages; } +DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, + const std::shared_ptr& data_packs, + BaseInstance* instance) + : ResourceDownloadDialog(parent, data_packs), m_instance(instance) +{ + setWindowTitle(dialogTitle()); + + initializeContainer(); + connectButtons(); + + if (!geometrySaveKey().isEmpty()) + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); +} + +QList DataPackDownloadDialog::getPages() +{ + QList pages; + pages.append(ModrinthDataPackPage::create(this, *m_instance)); + return pages; +} + } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index e9d2cfbe6..f5599041d 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -25,6 +25,7 @@ #include #include "QObjectPtr.h" +#include "minecraft/mod/DataPackFolderModel.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" #include "ui/pages/BasePageProvider.h" @@ -166,4 +167,21 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { BaseInstance* m_instance; }; +class DataPackDownloadDialog final : public ResourceDownloadDialog { + Q_OBJECT + + public: + explicit DataPackDownloadDialog(QWidget* parent, const std::shared_ptr& data_packs, BaseInstance* instance); + ~DataPackDownloadDialog() override = default; + + //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourcesString() const override { return tr("data packs"); } + [[nodiscard]] QString geometrySaveKey() const override { return "DataPackDownloadGeometry"; } + + QList getPages() override; + + private: + BaseInstance* m_instance; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp new file mode 100644 index 000000000..f46e7528f --- /dev/null +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "DataPackPage.h" + +#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/ResourceDownloadDialog.h" + +DataPackPage::DataPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) + : ExternalResourcesPage(instance, model, parent) +{ + ui->actionDownloadItem->setText(tr("Download packs")); + ui->actionDownloadItem->setToolTip(tr("Download data packs from online platforms")); + ui->actionDownloadItem->setEnabled(true); + connect(ui->actionDownloadItem, &QAction::triggered, this, &DataPackPage::downloadDataPacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); + + ui->actionViewConfigs->setVisible(false); +} + +bool DataPackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) +{ + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& dp = static_cast(m_model->at(row)); + ui->frame->updateWithDataPack(dp); + + return true; +} + +void DataPackPage::downloadDataPacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + ResourceDownload::DataPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); + if (mdownload.exec()) { + auto tasks = + new ConcurrentTask(this, "Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h new file mode 100644 index 000000000..039a9c40f --- /dev/null +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ExternalResourcesPage.h" +#include "minecraft/mod/DataPackFolderModel.h" +#include "ui_ExternalResourcesPage.h" + +class DataPackPage : public ExternalResourcesPage { + Q_OBJECT + public: + explicit DataPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = 0); + + QString displayName() const override { return tr("Data packs"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("datapacks"); } + QString id() const override { return "datapacks"; } + QString helpPage() const override { return "Data-packs"; } + bool shouldDisplay() const override { return true; } + + public slots: + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + void downloadDataPacks(); +}; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index d29be0fc3..031935544 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 587bb6ce6..133957328 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -57,6 +57,7 @@ #include "ui/GuiUtil.h" #include "Application.h" +#include "DataPackPage.h" class WorldListProxyModel : public QSortFilterProxyModel { Q_OBJECT @@ -82,7 +83,7 @@ class WorldListProxyModel : public QSortFilterProxyModel { } }; -WorldListPage::WorldListPage(BaseInstance* inst, std::shared_ptr worlds, QWidget* parent) +WorldListPage::WorldListPage(MinecraftInstance* inst, std::shared_ptr worlds, QWidget* parent) : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { ui->setupUi(this); @@ -210,7 +211,7 @@ void WorldListPage::on_actionView_Folder_triggered() DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); } -void WorldListPage::on_actionDatapacks_triggered() +void WorldListPage::on_actionData_Packs_triggered() { QModelIndex index = getSelectedWorld(); @@ -218,12 +219,33 @@ void WorldListPage::on_actionDatapacks_triggered() return; } - if (!worldSafetyNagQuestion(tr("Open World Datapacks Folder"))) + if (!worldSafetyNagQuestion(tr("Manage Data Packs"))) return; - auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + const QString fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + const QString folder = FS::PathCombine(fullPath, "datapacks"); - DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true); + auto dialog = new QDialog(window()); + dialog->setWindowTitle(tr("Data packs for %1").arg(m_worlds->data(index, WorldList::NameRole).toString())); + dialog->setWindowModality(Qt::WindowModal); + + dialog->resize(static_cast(std::max(0.5 * window()->width(), 400.0)), + static_cast(std::max(0.75 * window()->height(), 400.0))); + dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray())); + + auto layout = new QHBoxLayout(dialog); + auto page = new DataPackPage(m_inst, std::make_shared(folder, m_inst)); + page->setParent(dialog); // HACK: many pages extend QMainWindow; setting the parent manually prevents them from creating a window. + layout->addWidget(page); + dialog->setLayout(layout); + + connect(dialog, &QDialog::finished, this, [dialog, page] { + APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); + page->closed(); + }); + + dialog->show(); + page->opened(); } void WorldListPage::on_actionReset_Icon_triggered() @@ -336,7 +358,7 @@ void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[ ui->actionRemove->setEnabled(enable); ui->actionCopy->setEnabled(enable); ui->actionRename->setEnabled(enable); - ui->actionDatapacks->setEnabled(enable); + ui->actionData_Packs->setEnabled(enable); bool hasIcon = !index.data(WorldList::IconFileRole).isNull(); ui->actionReset_Icon->setEnabled(enable && hasIcon); } diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 4f83002f4..50c5d20f6 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -53,7 +53,7 @@ class WorldListPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit WorldListPage(BaseInstance* inst, std::shared_ptr worlds, QWidget* parent = 0); + explicit WorldListPage(MinecraftInstance* inst, std::shared_ptr worlds, QWidget* parent = 0); virtual ~WorldListPage(); virtual QString displayName() const override { return tr("Worlds"); } @@ -72,7 +72,7 @@ class WorldListPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; protected: - BaseInstance* m_inst; + MinecraftInstance* m_inst; private: QModelIndex getSelectedWorld(); @@ -97,7 +97,7 @@ class WorldListPage : public QMainWindow, public BasePage { void on_actionRename_triggered(); void on_actionRefresh_triggered(); void on_actionView_Folder_triggered(); - void on_actionDatapacks_triggered(); + void on_actionData_Packs_triggered(); void on_actionReset_Icon_triggered(); void worldChanged(const QModelIndex& current, const QModelIndex& previous); void mceditState(LoggedProcess::State state); diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui index d74dd0796..b30b691d3 100644 --- a/launcher/ui/pages/instance/WorldListPage.ui +++ b/launcher/ui/pages/instance/WorldListPage.ui @@ -85,7 +85,7 @@ - + @@ -140,12 +140,12 @@ Remove world icon to make the game re-generate it on next load. - + - Datapacks + Data Packs - Manage datapacks inside the world. + Manage data packs inside the world. diff --git a/launcher/ui/pages/modplatform/DataPackModel.cpp b/launcher/ui/pages/modplatform/DataPackModel.cpp new file mode 100644 index 000000000..c17703d3c --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackModel.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#include "DataPackModel.h" + +#include + +namespace ResourceDownload { + +DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) + : ResourceModel(api), m_base_instance(base_inst) +{} + +/******** Make data requests ********/ + +ResourceAPI::SearchArgs DataPackResourceModel::createSearchArguments() +{ + auto sort = getCurrentSortingMethodByIndex(); + return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort }; +} + +ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { *pack }; +} + +ResourceAPI::ProjectInfoArgs DataPackResourceModel::createInfoArguments(QModelIndex& entry) +{ + auto& pack = m_packs[entry.row()]; + return { *pack }; +} + +void DataPackResourceModel::searchWithTerm(const QString& term, unsigned int sort) +{ + if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) { + return; + } + + setSearchTerm(term); + m_current_sort_index = sort; + + refresh(); +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackModel.h b/launcher/ui/pages/modplatform/DataPackModel.h new file mode 100644 index 000000000..4954b7350 --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackModel.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +#include "BaseInstance.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourceModel.h" + +class Version; + +namespace ResourceDownload { + +class DataPackResourceModel : public ResourceModel { + Q_OBJECT + + public: + DataPackResourceModel(BaseInstance const&, ResourceAPI*); + + /* Ask the API for more information */ + void searchWithTerm(const QString& term, unsigned int sort); + + void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0; + void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0; + + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + + protected: + const BaseInstance& m_base_instance; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackPage.cpp b/launcher/ui/pages/modplatform/DataPackPage.cpp new file mode 100644 index 000000000..84a777f3c --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackPage.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#include "DataPackPage.h" +#include "modplatform/ModIndex.h" +#include "ui_ResourcePage.h" + +#include "DataPackModel.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" + +#include + +namespace ResourceDownload { + +DataPackResourcePage::DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) +{ + connect(m_ui->searchButton, &QPushButton::clicked, this, &DataPackResourcePage::triggerSearch); + connect(m_ui->packView, &QListView::doubleClicked, this, &DataPackResourcePage::onResourceSelected); +} + +/******** Callbacks to events in the UI (set up in the derived classes) ********/ + +void DataPackResourcePage::triggerSearch() +{ + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); + + updateSelectionButton(); + + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt()); + m_fetch_progress.watch(m_model->activeSearchJob().get()); +} + +QMap DataPackResourcePage::urlHandlers() const +{ + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/resourcepack\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/texture-packs\\/([^\\/]+)\\/?"), + "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; +} + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/DataPackPage.h b/launcher/ui/pages/modplatform/DataPackPage.h new file mode 100644 index 000000000..55ed205f8 --- /dev/null +++ b/launcher/ui/pages/modplatform/DataPackPage.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2023 flowln +// SPDX-FileCopyrightText: 2023 TheKodeToad +// +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/pages/modplatform/DataPackModel.h" + +namespace Ui { +class ResourcePage; +} + +namespace ResourceDownload { + +class DataPackDownloadDialog; + +class DataPackResourcePage : public ResourcePage { + Q_OBJECT + + public: + template + static T* create(DataPackDownloadDialog* dialog, BaseInstance& instance) + { + auto page = new T(dialog, instance); + auto model = static_cast(page->getModel()); + + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + + return page; + } + + //: The plural version of 'data pack' + [[nodiscard]] inline QString resourcesString() const override { return tr("data packs"); } + //: The singular version of 'data packs' + [[nodiscard]] inline QString resourceString() const override { return tr("data pack"); } + + [[nodiscard]] bool supportsFiltering() const override { return false; }; + + [[nodiscard]] QMap urlHandlers() const override; + + protected: + DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance); + + protected slots: + void triggerSearch() override; +}; + +} // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 856018294..a2185233d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -118,4 +118,27 @@ auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJson return obj.object().value("hits").toArray(); } +ModrinthDataPackModel::ModrinthDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new ModrinthAPI) {} + +void ModrinthDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadIndexedPack(m, obj); +} + +void ModrinthDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + ::Modrinth::loadExtraPackData(m, obj); +} + +void ModrinthDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); +} + +auto ModrinthDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h index 15cd58544..6a5ba0382 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -20,6 +20,7 @@ #pragma once +#include "ui/pages/modplatform/DataPackModel.h" #include "ui/pages/modplatform/ModModel.h" #include "ui/pages/modplatform/ResourcePackModel.h" #include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" @@ -99,4 +100,22 @@ class ModrinthShaderPackModel : public ShaderPackResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class ModrinthDataPackModel : public DataPackResourceModel { + Q_OBJECT + + public: + ModrinthDataPackModel(const BaseInstance&); + ~ModrinthDataPackModel() override = default; + + private: + [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Modrinth::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index a4197b225..d4adf07d9 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -124,6 +124,24 @@ ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, m_ui->packDescription->setMetaEntry(metaEntryBase()); } +ModrinthDataPackPage::ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) + : DataPackResourcePage(dialog, instance) +{ + m_model = new ModrinthDataPackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthDataPackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthDataPackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthDataPackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + // I don't know why, but doing this on the parent class makes it so that // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... @@ -143,5 +161,9 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool { return true; } +auto ModrinthDataPackPage::shouldDisplay() const -> bool +{ + return true; +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 311bcfe32..e9cb33a60 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -41,6 +41,7 @@ #include "modplatform/ResourceAPI.h" +#include "ui/pages/modplatform/DataPackPage.h" #include "ui/pages/modplatform/ModPage.h" #include "ui/pages/modplatform/ResourcePackPage.h" #include "ui/pages/modplatform/ShaderPackPage.h" @@ -166,4 +167,27 @@ class ModrinthShaderPackPage : public ShaderPackResourcePage { [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } }; +class ModrinthDataPackPage : public DataPackResourcePage { + Q_OBJECT + + public: + static ModrinthDataPackPage* create(DataPackDownloadDialog* dialog, BaseInstance& instance) + { + return DataPackResourcePage::create(dialog, instance); + } + + ModrinthDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthDataPackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } +}; + } // namespace ResourceDownload diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 69f72fea2..f44e1e3ff 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -212,6 +212,12 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) setImage(resource_pack.image({ 64, 64 })); } +void InfoFrame::updateWithDataPack(DataPack& data_pack) { + setName(renderColorCodes(data_pack.name())); + setDescription(renderColorCodes(data_pack.description())); + setImage(data_pack.image({ 64, 64 })); +} + void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { setName(renderColorCodes(texture_pack.name())); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index d6764baa2..20c54e2e5 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -37,6 +37,7 @@ #include +#include "minecraft/mod/DataPack.h" #include "minecraft/mod/Mod.h" #include "minecraft/mod/ResourcePack.h" #include "minecraft/mod/TexturePack.h" @@ -63,6 +64,7 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); void updateWithResourcePack(ResourcePack& rp); + void updateWithDataPack(DataPack& rp); void updateWithTexturePack(TexturePack& tp); static QString renderColorCodes(QString input); From 18ee1f897ad95ce6f61f9d14779985745b271498 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 25 Mar 2025 16:28:16 +0000 Subject: [PATCH 02/22] Global data packs Signed-off-by: TheKodeToad --- launcher/InstancePageProvider.h | 2 + launcher/minecraft/MinecraftInstance.cpp | 27 ++++- launcher/minecraft/MinecraftInstance.h | 4 + .../resources/breeze_dark/breeze_dark.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 .../resources/breeze_light/breeze_light.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/flat/flat.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/flat_white/flat_white.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/multimc/multimc.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/pe_blue/pe_blue.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/pe_colored/pe_colored.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/pe_dark/pe_dark.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/resources/pe_light/pe_light.qrc | 2 +- ...nvironment-variables.svg => datapacks.svg} | 0 launcher/ui/pages/instance/DataPackPage.cpp | 99 ++++++++++++++++++- launcher/ui/pages/instance/DataPackPage.h | 32 +++++- launcher/ui/pages/instance/WorldListPage.cpp | 5 +- .../ui/widgets/MinecraftSettingsWidget.cpp | 14 +++ .../ui/widgets/MinecraftSettingsWidget.ui | 77 +++++++++++++-- 26 files changed, 254 insertions(+), 24 deletions(-) rename launcher/resources/breeze_dark/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/breeze_light/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/flat/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/flat_white/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/multimc/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/pe_blue/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/pe_colored/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/pe_dark/scalable/{environment-variables.svg => datapacks.svg} (100%) rename launcher/resources/pe_light/scalable/{environment-variables.svg => datapacks.svg} (100%) diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 1d7c193f8..e4884d11f 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include "minecraft/MinecraftInstance.h" #include "ui/pages/BasePage.h" #include "ui/pages/BasePageProvider.h" @@ -36,6 +37,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider { values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList())); values.append(new NilModFolderPage(onesix.get(), onesix->nilModList())); values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList())); + values.append(new GlobalDataPackPage(onesix.get())); values.append(new TexturePackPage(onesix.get(), onesix->texturePackList())); values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList())); values.append(new NotesPage(onesix.get())); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d1780d497..ab4219627 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -250,6 +250,12 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerSetting("ExportAuthor", ""); m_settings->registerSetting("ExportOptionalFiles", true); + auto dataPacksEnabled = m_settings->registerSetting("GlobalDataPacksEnabled", false); + auto dataPacksPath = m_settings->registerSetting("GlobalDataPacksPath", ""); + + connect(dataPacksEnabled.get(), &Setting::SettingChanged, this, [this] { m_data_pack_list.reset(); }); + connect(dataPacksPath.get(), &Setting::SettingChanged, this, [this] { m_data_pack_list.reset(); }); + qDebug() << "Instance-type specific settings were loaded!"; setSpecificSettingsLoaded(true); @@ -392,6 +398,16 @@ QString MinecraftInstance::nilModsDir() const return FS::PathCombine(gameRoot(), "nilmods"); } +QString MinecraftInstance::dataPacksDir() +{ + QString relativePath = settings()->get("GlobalDataPacksPath").toString(); + + if (relativePath.isEmpty()) + relativePath = "datapacks"; + + return FS::PathCombine(gameRoot(), relativePath); +} + QString MinecraftInstance::resourcePacksDir() const { return FS::PathCombine(gameRoot(), "resourcepacks"); @@ -1303,9 +1319,18 @@ std::shared_ptr MinecraftInstance::shaderPackList() return m_shader_pack_list; } +std::shared_ptr MinecraftInstance::dataPackList() +{ + if (!m_data_pack_list && settings()->get("GlobalDataPacksEnabled").toBool()) { + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_data_pack_list.reset(new DataPackFolderModel(dataPacksDir(), this, isIndexed, true)); + } + return m_data_pack_list; +} + QList> MinecraftInstance::resourceLists() { - return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() }; + return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList(), dataPackList() }; } std::shared_ptr MinecraftInstance::worldList() diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 5d9bb45ef..68f5d4f2a 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -36,6 +36,7 @@ #pragma once #include +#include #include #include #include "BaseInstance.h" @@ -80,6 +81,7 @@ class MinecraftInstance : public BaseInstance { QString modsRoot() const override; QString coreModsDir() const; QString nilModsDir() const; + QString dataPacksDir(); QString modsCacheLocation() const; QString libDir() const; QString worldDir() const; @@ -116,6 +118,7 @@ class MinecraftInstance : public BaseInstance { std::shared_ptr resourcePackList(); std::shared_ptr texturePackList(); std::shared_ptr shaderPackList(); + std::shared_ptr dataPackList(); QList> resourceLists(); std::shared_ptr worldList(); std::shared_ptr gameOptionsModel(); @@ -171,6 +174,7 @@ class MinecraftInstance : public BaseInstance { mutable std::shared_ptr m_resource_pack_list; mutable std::shared_ptr m_shader_pack_list; mutable std::shared_ptr m_texture_pack_list; + mutable std::shared_ptr m_data_pack_list; mutable std::shared_ptr m_world_list; mutable std::shared_ptr m_game_options; }; diff --git a/launcher/resources/breeze_dark/breeze_dark.qrc b/launcher/resources/breeze_dark/breeze_dark.qrc index 61d82ec30..8be5d117b 100644 --- a/launcher/resources/breeze_dark/breeze_dark.qrc +++ b/launcher/resources/breeze_dark/breeze_dark.qrc @@ -9,7 +9,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/discord.svg scalable/externaltools.svg scalable/help.svg diff --git a/launcher/resources/breeze_dark/scalable/environment-variables.svg b/launcher/resources/breeze_dark/scalable/datapacks.svg similarity index 100% rename from launcher/resources/breeze_dark/scalable/environment-variables.svg rename to launcher/resources/breeze_dark/scalable/datapacks.svg diff --git a/launcher/resources/breeze_light/breeze_light.qrc b/launcher/resources/breeze_light/breeze_light.qrc index 2211c7188..291bcb508 100644 --- a/launcher/resources/breeze_light/breeze_light.qrc +++ b/launcher/resources/breeze_light/breeze_light.qrc @@ -9,7 +9,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/discord.svg scalable/externaltools.svg scalable/help.svg diff --git a/launcher/resources/breeze_light/scalable/environment-variables.svg b/launcher/resources/breeze_light/scalable/datapacks.svg similarity index 100% rename from launcher/resources/breeze_light/scalable/environment-variables.svg rename to launcher/resources/breeze_light/scalable/datapacks.svg diff --git a/launcher/resources/flat/flat.qrc b/launcher/resources/flat/flat.qrc index 8876027da..9f88645c2 100644 --- a/launcher/resources/flat/flat.qrc +++ b/launcher/resources/flat/flat.qrc @@ -11,7 +11,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/discord.svg scalable/externaltools.svg scalable/help.svg diff --git a/launcher/resources/flat/scalable/environment-variables.svg b/launcher/resources/flat/scalable/datapacks.svg similarity index 100% rename from launcher/resources/flat/scalable/environment-variables.svg rename to launcher/resources/flat/scalable/datapacks.svg diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc index 83b178cbf..56aad62fa 100644 --- a/launcher/resources/flat_white/flat_white.qrc +++ b/launcher/resources/flat_white/flat_white.qrc @@ -11,7 +11,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/discord.svg scalable/externaltools.svg scalable/help.svg diff --git a/launcher/resources/flat_white/scalable/environment-variables.svg b/launcher/resources/flat_white/scalable/datapacks.svg similarity index 100% rename from launcher/resources/flat_white/scalable/environment-variables.svg rename to launcher/resources/flat_white/scalable/datapacks.svg diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc index 25edd09e0..853dda525 100644 --- a/launcher/resources/multimc/multimc.qrc +++ b/launcher/resources/multimc/multimc.qrc @@ -74,7 +74,7 @@ scalable/screenshots.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg 16x16/cat.png diff --git a/launcher/resources/multimc/scalable/environment-variables.svg b/launcher/resources/multimc/scalable/datapacks.svg similarity index 100% rename from launcher/resources/multimc/scalable/environment-variables.svg rename to launcher/resources/multimc/scalable/datapacks.svg diff --git a/launcher/resources/pe_blue/pe_blue.qrc b/launcher/resources/pe_blue/pe_blue.qrc index 717d3972e..df3a71ab2 100644 --- a/launcher/resources/pe_blue/pe_blue.qrc +++ b/launcher/resources/pe_blue/pe_blue.qrc @@ -10,7 +10,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/externaltools.svg scalable/help.svg scalable/instance-settings.svg diff --git a/launcher/resources/pe_blue/scalable/environment-variables.svg b/launcher/resources/pe_blue/scalable/datapacks.svg similarity index 100% rename from launcher/resources/pe_blue/scalable/environment-variables.svg rename to launcher/resources/pe_blue/scalable/datapacks.svg diff --git a/launcher/resources/pe_colored/pe_colored.qrc b/launcher/resources/pe_colored/pe_colored.qrc index 023c81e74..1a08f7e18 100644 --- a/launcher/resources/pe_colored/pe_colored.qrc +++ b/launcher/resources/pe_colored/pe_colored.qrc @@ -10,7 +10,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/externaltools.svg scalable/help.svg scalable/instance-settings.svg diff --git a/launcher/resources/pe_colored/scalable/environment-variables.svg b/launcher/resources/pe_colored/scalable/datapacks.svg similarity index 100% rename from launcher/resources/pe_colored/scalable/environment-variables.svg rename to launcher/resources/pe_colored/scalable/datapacks.svg diff --git a/launcher/resources/pe_dark/pe_dark.qrc b/launcher/resources/pe_dark/pe_dark.qrc index c97fb469c..9d3d3d46c 100644 --- a/launcher/resources/pe_dark/pe_dark.qrc +++ b/launcher/resources/pe_dark/pe_dark.qrc @@ -10,7 +10,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/externaltools.svg scalable/help.svg scalable/instance-settings.svg diff --git a/launcher/resources/pe_dark/scalable/environment-variables.svg b/launcher/resources/pe_dark/scalable/datapacks.svg similarity index 100% rename from launcher/resources/pe_dark/scalable/environment-variables.svg rename to launcher/resources/pe_dark/scalable/datapacks.svg diff --git a/launcher/resources/pe_light/pe_light.qrc b/launcher/resources/pe_light/pe_light.qrc index b590dd2c6..2775a0872 100644 --- a/launcher/resources/pe_light/pe_light.qrc +++ b/launcher/resources/pe_light/pe_light.qrc @@ -10,7 +10,7 @@ scalable/copy.svg scalable/coremods.svg scalable/custom-commands.svg - scalable/environment-variables.svg + scalable/datapacks.svg scalable/externaltools.svg scalable/help.svg scalable/instance-settings.svg diff --git a/launcher/resources/pe_light/scalable/environment-variables.svg b/launcher/resources/pe_light/scalable/datapacks.svg similarity index 100% rename from launcher/resources/pe_light/scalable/environment-variables.svg rename to launcher/resources/pe_light/scalable/datapacks.svg diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index 833a72301..ddc12d7b3 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -22,7 +22,7 @@ #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" -DataPackPage::DataPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent) +DataPackPage::DataPackPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(instance, model, parent) { ui->actionDownloadItem->setText(tr("Download packs")); @@ -30,7 +30,7 @@ DataPackPage::DataPackPage(MinecraftInstance* instance, std::shared_ptractionDownloadItem->setEnabled(true); connect(ui->actionDownloadItem, &QAction::triggered, this, &DataPackPage::downloadDataPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - + ui->actionViewConfigs->setVisible(false); } @@ -49,8 +49,7 @@ void DataPackPage::downloadDataPacks() ResourceDownload::DataPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); if (mdownload.exec()) { - auto tasks = - new ConcurrentTask("Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto tasks = new ConcurrentTask("Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -78,3 +77,95 @@ void DataPackPage::downloadDataPacks() m_model->update(); } } + +GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent) : QWidget(parent), m_instance(instance) +{ + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + connect(instance->settings()->getSetting("GlobalDataPacksEnabled").get(), &Setting::SettingChanged, this, [this] { + updateContent(); + if (m_container != nullptr) + m_container->refreshContainer(); + }); + + connect(instance->settings()->getSetting("GlobalDataPacksPath").get(), &Setting::SettingChanged, this, + &GlobalDataPackPage::updateContent); +} + +QString GlobalDataPackPage::displayName() const +{ + if (m_underlyingPage == nullptr) + return {}; + + return m_underlyingPage->displayName(); +} + +QIcon GlobalDataPackPage::icon() const +{ + if (m_underlyingPage == nullptr) + return {}; + + return m_underlyingPage->icon(); +} + +QString GlobalDataPackPage::helpPage() const +{ + if (m_underlyingPage == nullptr) + return {}; + + return m_underlyingPage->helpPage(); +} + +bool GlobalDataPackPage::shouldDisplay() const +{ + return m_instance->settings()->get("GlobalDataPacksEnabled").toBool(); +} + +bool GlobalDataPackPage::apply() +{ + return m_underlyingPage != nullptr && m_underlyingPage->apply(); +} + +void GlobalDataPackPage::openedImpl() +{ + if (m_underlyingPage != nullptr) + m_underlyingPage->openedImpl(); +} + +void GlobalDataPackPage::closedImpl() +{ + if (m_underlyingPage != nullptr) + m_underlyingPage->closedImpl(); +} + +void GlobalDataPackPage::updateContent() +{ + if (m_underlyingPage != nullptr) { + if (m_container->selectedPage() == this) + m_underlyingPage->closedImpl(); + + m_underlyingPage->apply(); + + layout()->removeWidget(m_underlyingPage); + + delete m_underlyingPage; + m_underlyingPage = nullptr; + } + + if (shouldDisplay()) { + m_underlyingPage = new DataPackPage(m_instance, m_instance->dataPackList()); + + if (m_container->selectedPage() == this) + m_underlyingPage->openedImpl(); + + layout()->addWidget(m_underlyingPage); + } +} + +void GlobalDataPackPage::setParentContainer(BasePageContainer* container) +{ + BasePage::setParentContainer(container); + updateContent(); +} diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h index 2ea284e31..781dd1092 100644 --- a/launcher/ui/pages/instance/DataPackPage.h +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -25,9 +25,9 @@ class DataPackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit DataPackPage(MinecraftInstance* instance, std::shared_ptr model, QWidget* parent = 0); + explicit DataPackPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); - QString displayName() const override { return tr("Data packs"); } + QString displayName() const override { return QObject::tr("Data packs"); } QIcon icon() const override { return APPLICATION->getThemedIcon("datapacks"); } QString id() const override { return "datapacks"; } QString helpPage() const override { return "Data-packs"; } @@ -37,3 +37,31 @@ class DataPackPage : public ExternalResourcesPage { void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; void downloadDataPacks(); }; + +/** + * Syncs DataPackPage with GlobalDataPacksPath and shows/hides based on GlobalDataPacksEnabled. + */ +class GlobalDataPackPage : public QWidget, public BasePage { + public: + explicit GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent = nullptr); + + QString displayName() const override; + QIcon icon() const override; + QString id() const override { return "datapacks"; } + QString helpPage() const override; + + bool shouldDisplay() const override; + + bool apply() override; + void openedImpl() override; + void closedImpl() override; + + void setParentContainer(BasePageContainer *container) override; + + private: + void updateContent(); + QVBoxLayout* layout() { return static_cast(QWidget::layout()); } + + MinecraftInstance* m_instance; + DataPackPage* m_underlyingPage = nullptr; +}; \ No newline at end of file diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 7cf4a22a5..96d16f681 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -238,8 +238,11 @@ void WorldListPage::on_actionData_Packs_triggered() static_cast(std::max(0.75 * window()->height(), 400.0))); dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray())); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + auto model = std::make_shared(folder, m_inst.get(), isIndexed, true); + auto layout = new QHBoxLayout(dialog); - auto page = new DataPackPage(m_inst.get(), std::make_shared(folder, m_inst.get(), true, true)); + auto page = new DataPackPage(m_inst.get(), std::move(model)); page->setParent(dialog); // HACK: many pages extend QMainWindow; setting the parent manually prevents them from creating a window. layout->addWidget(page); dialog->setLayout(layout); diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index cec7f267f..eefffb73f 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -58,6 +58,8 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, } m_ui->openGlobalSettingsButton->setVisible(false); + + m_ui->globalDataPacksGroupBox->hide(); } else { m_javaSettings = new JavaSettingsWidget(m_instance, this); m_ui->javaScrollArea->setWidget(m_javaSettings); @@ -97,6 +99,11 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, connect(m_ui->openGlobalSettingsButton, &QCommandLinkButton::clicked, this, &MinecraftSettingsWidget::openGlobalSettings); connect(m_ui->serverJoinAddressButton, &QAbstractButton::toggled, m_ui->serverJoinAddress, &QWidget::setEnabled); connect(m_ui->worldJoinButton, &QAbstractButton::toggled, m_ui->worldsCb, &QWidget::setEnabled); + + connect(m_ui->globalDataPacksGroupBox, &QGroupBox::toggled, this, + [this](bool value) { m_instance->settings()->set("GlobalDataPacksEnabled", value); }); + connect(m_ui->dataPacksPathEdit, &QLineEdit::editingFinished, this, + [this]() { m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text()); }); } m_ui->maximizedWarning->hide(); @@ -231,6 +238,13 @@ void MinecraftSettingsWidget::loadSettings() m_ui->legacySettingsGroupBox->setChecked(settings->get("OverrideLegacySettings").toBool()); m_ui->onlineFixes->setChecked(settings->get("OnlineFixes").toBool()); + + m_ui->globalDataPacksGroupBox->blockSignals(true); + m_ui->dataPacksPathEdit->blockSignals(true); + m_ui->globalDataPacksGroupBox->setChecked(settings->get("GlobalDataPacksEnabled").toBool()); + m_ui->dataPacksPathEdit->setText(settings->get("GlobalDataPacksPath").toString()); + m_ui->globalDataPacksGroupBox->blockSignals(false); + m_ui->dataPacksPathEdit->blockSignals(false); } void MinecraftSettingsWidget::saveSettings() diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.ui b/launcher/ui/widgets/MinecraftSettingsWidget.ui index daa065ac8..61d90255f 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.ui +++ b/launcher/ui/widgets/MinecraftSettingsWidget.ui @@ -61,9 +61,9 @@ 0 - -253 + -166 610 - 550 + 768 @@ -149,6 +149,70 @@ + + + + &Global Data Packs + + + true + + + true + + + + + + Allows installing data packs across all worlds if an applicable mod is installed. +It is most likely you will need to change the path - please refer to the mod's website. + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 6 + + + + + + + + Folder Path + + + + + + + + + datapacks + + + + + + + Browse + + + + + + + + @@ -298,7 +362,7 @@ 0 0 624 - 297 + 291 @@ -323,9 +387,9 @@ 0 - -101 + 0 610 - 398 + 439 @@ -513,7 +577,7 @@ 0 0 624 - 297 + 291 @@ -647,7 +711,6 @@ openGlobalSettingsButton settingsTabs - scrollArea maximizedCheckBox windowWidthSpinBox windowHeightSpinBox From 481a1d222ccd897592953a712c18e94694980eb8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 25 Mar 2025 16:31:41 +0000 Subject: [PATCH 03/22] Fix use after free If any page apply methods return false, the launcher breaks when opening an instance window for the second time Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/DataPackPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index ddc12d7b3..3ca86850a 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -125,7 +125,7 @@ bool GlobalDataPackPage::shouldDisplay() const bool GlobalDataPackPage::apply() { - return m_underlyingPage != nullptr && m_underlyingPage->apply(); + return m_underlyingPage == nullptr || m_underlyingPage->apply(); } void GlobalDataPackPage::openedImpl() From 46c348a7b4f7d51bafffd4f3c364086b17c81b67 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 25 Mar 2025 17:18:55 +0000 Subject: [PATCH 04/22] Fix license header emails Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/DataPackModel.cpp | 2 +- launcher/ui/pages/modplatform/DataPackModel.h | 2 +- launcher/ui/pages/modplatform/DataPackPage.cpp | 2 +- launcher/ui/pages/modplatform/DataPackPage.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/modplatform/DataPackModel.cpp b/launcher/ui/pages/modplatform/DataPackModel.cpp index c17703d3c..598fe3ca3 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.cpp +++ b/launcher/ui/pages/modplatform/DataPackModel.cpp @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2023 flowln -// SPDX-FileCopyrightText: 2023 TheKodeToad +// SPDX-FileCopyrightText: 2023 TheKodeToad // // SPDX-License-Identifier: GPL-3.0-only diff --git a/launcher/ui/pages/modplatform/DataPackModel.h b/launcher/ui/pages/modplatform/DataPackModel.h index 4954b7350..54da2404c 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.h +++ b/launcher/ui/pages/modplatform/DataPackModel.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2023 flowln -// SPDX-FileCopyrightText: 2023 TheKodeToad +// SPDX-FileCopyrightText: 2023 TheKodeToad // // SPDX-License-Identifier: GPL-3.0-only diff --git a/launcher/ui/pages/modplatform/DataPackPage.cpp b/launcher/ui/pages/modplatform/DataPackPage.cpp index 397a33e96..2b506ca67 100644 --- a/launcher/ui/pages/modplatform/DataPackPage.cpp +++ b/launcher/ui/pages/modplatform/DataPackPage.cpp @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2023 flowln -// SPDX-FileCopyrightText: 2023 TheKodeToad +// SPDX-FileCopyrightText: 2023 TheKodeToad // // SPDX-License-Identifier: GPL-3.0-only diff --git a/launcher/ui/pages/modplatform/DataPackPage.h b/launcher/ui/pages/modplatform/DataPackPage.h index 55ed205f8..2e622ebd4 100644 --- a/launcher/ui/pages/modplatform/DataPackPage.h +++ b/launcher/ui/pages/modplatform/DataPackPage.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2023 flowln -// SPDX-FileCopyrightText: 2023 TheKodeToad +// SPDX-FileCopyrightText: 2023 TheKodeToad // // SPDX-License-Identifier: GPL-3.0-only From 82978ee34d46c654c306ccb64a99efada9ad01c3 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 25 Mar 2025 19:23:35 +0000 Subject: [PATCH 05/22] Fix data pack filtering Signed-off-by: TheKodeToad --- launcher/modplatform/ModIndex.cpp | 4 +++- launcher/modplatform/ModIndex.h | 10 +++++++++- launcher/modplatform/flame/FlameAPI.h | 2 ++ launcher/modplatform/modrinth/ModrinthAPI.h | 12 ++++++------ launcher/ui/pages/modplatform/DataPackModel.cpp | 4 ++-- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index c3ecccf8e..380ff660f 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -122,11 +122,13 @@ auto getModLoaderAsString(ModLoaderType type) -> const QString case Cauldron: return "cauldron"; case LiteLoader: - return "liteloader"; + return "liteloader"; case Fabric: return "fabric"; case Quilt: return "quilt"; + case DataPack: + return "datapack"; default: break; } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 1c8507f12..74024ba0e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -30,7 +30,15 @@ class QIODevice; namespace ModPlatform { -enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 }; +enum ModLoaderType { + NeoForge = 1 << 0, + Forge = 1 << 1, + Cauldron = 1 << 2, + LiteLoader = 1 << 3, + Fabric = 1 << 4, + Quilt = 1 << 5, + DataPack = 1 << 6 +}; Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) QList modLoaderTypesToList(ModLoaderTypes flags); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 802c67a35..14e4effec 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -68,6 +68,8 @@ class FlameAPI : public NetworkResourceAPI { return 5; case ModPlatform::NeoForge: return 6; + case ModPlatform::DataPack: + break; // not supported } return 0; } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 26e2f423a..17b23723b 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -42,8 +42,8 @@ class ModrinthAPI : public NetworkResourceAPI { static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList { QStringList l; - for (auto loader : - { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) { + for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader, + ModPlatform::DataPack }) { if (types & loader) { l << getModLoaderAsString(loader); } @@ -103,12 +103,13 @@ class ModrinthAPI : public NetworkResourceAPI { { switch (type) { case ModPlatform::ResourceType::MOD: - case ModPlatform::ResourceType::DATA_PACK: return "mod"; case ModPlatform::ResourceType::RESOURCE_PACK: return "resourcepack"; case ModPlatform::ResourceType::SHADER_PACK: return "shader"; + case ModPlatform::ResourceType::DATA_PACK: + return "datapack"; case ModPlatform::ResourceType::MODPACK: return "modpack"; default: @@ -127,8 +128,6 @@ class ModrinthAPI : public NetworkResourceAPI { facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); if (args.versions.has_value() && !args.versions.value().empty()) facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); - if (args.type == ModPlatform::ResourceType::DATA_PACK) - facets_list.append("[\"categories:datapack\"]"); if (args.side.has_value()) { auto side = getSideFilters(args.side.value()); if (!side.isEmpty()) @@ -200,7 +199,8 @@ class ModrinthAPI : public NetworkResourceAPI { static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool { - return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader); + return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader | + ModPlatform::DataPack); } [[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override diff --git a/launcher/ui/pages/modplatform/DataPackModel.cpp b/launcher/ui/pages/modplatform/DataPackModel.cpp index 598fe3ca3..4b537cda9 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.cpp +++ b/launcher/ui/pages/modplatform/DataPackModel.cpp @@ -18,13 +18,13 @@ DataPackResourceModel::DataPackResourceModel(BaseInstance const& base_inst, Reso ResourceAPI::SearchArgs DataPackResourceModel::createSearchArguments() { auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort }; + return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort, ModPlatform::ModLoaderType::DataPack }; } ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(QModelIndex& entry) { auto& pack = m_packs[entry.row()]; - return { *pack }; + return { *pack, {}, ModPlatform::ModLoaderType::DataPack }; } ResourceAPI::ProjectInfoArgs DataPackResourceModel::createInfoArguments(QModelIndex& entry) From ed96f2064bf75373d72baab6a3901cd0c52558c1 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 25 Mar 2025 23:23:49 +0000 Subject: [PATCH 06/22] Display selected count and add buttons to dialog Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/DataPackPage.cpp | 3 +- launcher/ui/pages/instance/WorldListPage.cpp | 44 +++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index 3ca86850a..f99d618b0 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -156,6 +156,8 @@ void GlobalDataPackPage::updateContent() if (shouldDisplay()) { m_underlyingPage = new DataPackPage(m_instance, m_instance->dataPackList()); + m_underlyingPage->setParentContainer(m_container); + m_underlyingPage->updateExtraInfo = [this](QString id, QString value) { updateExtraInfo(std::move(id), std::move(value)); }; if (m_container->selectedPage() == this) m_underlyingPage->openedImpl(); @@ -163,7 +165,6 @@ void GlobalDataPackPage::updateContent() layout()->addWidget(m_underlyingPage); } } - void GlobalDataPackPage::setParentContainer(BasePageContainer* container) { BasePage::setParentContainer(container); diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 96d16f681..f1e00a799 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -40,7 +40,9 @@ #include "ui/dialogs/CustomMessageBox.h" #include "ui_WorldListPage.h" +#include #include +#include #include #include #include @@ -49,6 +51,7 @@ #include #include #include +#include #include "FileSystem.h" #include "tools/MCEditTool.h" @@ -230,7 +233,7 @@ void WorldListPage::on_actionData_Packs_triggered() const QString fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); const QString folder = FS::PathCombine(fullPath, "datapacks"); - auto dialog = new QDialog(window()); + auto dialog = new QDialog(this); dialog->setWindowTitle(tr("Data packs for %1").arg(m_worlds->data(index, WorldList::NameRole).toString())); dialog->setWindowModality(Qt::WindowModal); @@ -238,22 +241,35 @@ void WorldListPage::on_actionData_Packs_triggered() static_cast(std::max(0.75 * window()->height(), 400.0))); dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray())); - bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - auto model = std::make_shared(folder, m_inst.get(), isIndexed, true); + GenericPageProvider provider(dialog->windowTitle()); - auto layout = new QHBoxLayout(dialog); - auto page = new DataPackPage(m_inst.get(), std::move(model)); - page->setParent(dialog); // HACK: many pages extend QMainWindow; setting the parent manually prevents them from creating a window. - layout->addWidget(page); - dialog->setLayout(layout); - - connect(dialog, &QDialog::finished, this, [dialog, page] { - APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); - page->closed(); + provider.addPageCreator([this, folder] { + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + auto model = std::make_shared(folder, m_inst.get(), isIndexed, true); + return new DataPackPage(m_inst.get(), std::move(model)); }); - dialog->show(); - page->opened(); + auto layout = new QVBoxLayout(dialog); + + auto focusStealer = new QPushButton(dialog); + layout->addWidget(focusStealer); + focusStealer->setDefault(true); + focusStealer->hide(); + + auto pageContainer = new PageContainer(&provider, {}, dialog); + pageContainer->hidePageList(); + layout->addWidget(pageContainer); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::Help); + connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::helpRequested, pageContainer, &PageContainer::help); + layout->addWidget(buttonBox); + + dialog->setLayout(layout); + + dialog->exec(); + + APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); } void WorldListPage::on_actionReset_Icon_triggered() From ea82d44aab908e830126b778124fc76a4ba45210 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 00:30:00 +0000 Subject: [PATCH 07/22] Allow absolute path for global data packs Signed-off-by: TheKodeToad --- launcher/minecraft/MinecraftInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ab4219627..8cb0634a3 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -405,7 +405,7 @@ QString MinecraftInstance::dataPacksDir() if (relativePath.isEmpty()) relativePath = "datapacks"; - return FS::PathCombine(gameRoot(), relativePath); + return QDir(gameRoot()).filePath(relativePath); } QString MinecraftInstance::resourcePacksDir() const From 5bdc0b38710f329684bae3abac4dc6f6cc076cd8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 01:04:04 +0000 Subject: [PATCH 08/22] Implement browse for global data pack folder Signed-off-by: TheKodeToad --- .../ui/widgets/MinecraftSettingsWidget.cpp | 24 ++++++++++++++++++- launcher/ui/widgets/MinecraftSettingsWidget.h | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index eefffb73f..4cc30411e 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -36,6 +36,7 @@ */ #include "MinecraftSettingsWidget.h" +#include #include "Application.h" #include "BuildConfig.h" @@ -103,7 +104,8 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, connect(m_ui->globalDataPacksGroupBox, &QGroupBox::toggled, this, [this](bool value) { m_instance->settings()->set("GlobalDataPacksEnabled", value); }); connect(m_ui->dataPacksPathEdit, &QLineEdit::editingFinished, this, - [this]() { m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text()); }); + [this] { m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text()); }); + connect(m_ui->dataPacksPathBrowse, &QPushButton::clicked, this, &MinecraftSettingsWidget::selectDataPacksFolder); } m_ui->maximizedWarning->hide(); @@ -472,3 +474,23 @@ bool MinecraftSettingsWidget::isQuickPlaySupported() { return m_instance->traits().contains("feature:is_quick_play_singleplayer"); } + +void MinecraftSettingsWidget::selectDataPacksFolder() +{ + QString path = QFileDialog::getExistingDirectory(this, tr("Select Global Data Packs Folder"), m_instance->instanceRoot()); + + if (path.isEmpty()) + return; + + // if it's inside the instance dir, set path relative to .minecraft + // (so that if it's directly in instance dir it will still lead with .. but more than two levels up are kept absolute) + + const QUrl instanceRootUrl = QUrl::fromLocalFile(m_instance->instanceRoot()); + const QUrl pathUrl = QUrl::fromLocalFile(path); + + if (instanceRootUrl.isParentOf(pathUrl)) + path = QDir(m_instance->gameRoot()).relativeFilePath(path); + + m_ui->dataPacksPathEdit->setText(path); + m_instance->settings()->set("GlobalDataPacksPath", path); +} diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.h b/launcher/ui/widgets/MinecraftSettingsWidget.h index 86effb337..0f9e35b9c 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.h +++ b/launcher/ui/widgets/MinecraftSettingsWidget.h @@ -56,6 +56,7 @@ class MinecraftSettingsWidget : public QWidget { void openGlobalSettings(); void updateAccountsMenu(const SettingsObject& settings); bool isQuickPlaySupported(); + void selectDataPacksFolder(); MinecraftInstancePtr m_instance; Ui::MinecraftSettingsWidget* m_ui; From e22930fabf8e621f249fa587272712d4f5128eb8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 01:08:05 +0000 Subject: [PATCH 09/22] Fix build Signed-off-by: TheKodeToad --- .../mod/tasks/LocalResourcePackParseTask.cpp | 192 ------------------ .../mod/tasks/LocalResourcePackParseTask.h | 36 ---- .../ui/widgets/MinecraftSettingsWidget.cpp | 4 +- tests/MetaComponentParse_test.cpp | 3 +- 4 files changed, 4 insertions(+), 231 deletions(-) delete mode 100644 launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp delete mode 100644 launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp deleted file mode 100644 index db4b2e55c..000000000 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 flowln - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "LocalResourcePackParseTask.h" - -#include "FileSystem.h" -#include "Json.h" -#include "minecraft/mod/tasks/LocalDataPackParseTask.h" - -#include -#include -#include - -#include - -namespace ResourcePackUtils { - -QString buildStyle(const QJsonObject& obj) -{ - QStringList styles; - if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) { - styles << QString("color: %1;").arg(color); - } - if (obj.contains("bold")) { - QString weight = "normal"; - if (Json::ensureBoolean(obj, "bold", false)) { - weight = "bold"; - } - styles << QString("font-weight: %1;").arg(weight); - } - if (obj.contains("italic")) { - QString style = "normal"; - if (Json::ensureBoolean(obj, "italic", false)) { - style = "italic"; - } - styles << QString("font-style: %1;").arg(style); - } - - return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" ")); -} - -QString processComponent(const QJsonArray& value, bool strikethrough, bool underline) -{ - QString result; - for (auto current : value) - result += processComponent(current, strikethrough, underline); - return result; -} - -QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline) -{ - underline = Json::ensureBoolean(obj, "underlined", underline); - strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough); - - QString result = Json::ensureString(obj, "text"); - if (underline) { - result = QString("%1").arg(result); - } - if (strikethrough) { - result = QString("%1").arg(result); - } - // the extra needs to be a array - result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline); - if (auto style = buildStyle(obj); !style.isEmpty()) { - result = QString("%2").arg(style, result); - } - if (obj.contains("clickEvent")) { - auto click_event = Json::ensureObject(obj, "clickEvent"); - auto action = Json::ensureString(click_event, "action"); - auto value = Json::ensureString(click_event, "value"); - if (action == "open_url" && !value.isEmpty()) { - result = QString("%2").arg(value, result); - } - } - return result; -} - -QString processComponent(const QJsonValue& value, bool strikethrough, bool underline) -{ - if (value.isString()) { - return value.toString(); - } - if (value.isBool()) { - return value.toBool() ? "true" : "false"; - } - if (value.isDouble()) { - return QString::number(value.toDouble()); - } - if (value.isArray()) { - return processComponent(value.toArray(), strikethrough, underline); - } - if (value.isObject()) { - return processComponent(value.toObject(), strikethrough, underline); - } - qWarning() << "Invalid component type!"; - return {}; -} - -bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data) -{ - auto img = QImage::fromData(raw_data); - if (!img.isNull()) { - pack->setImage(img); - } else { - qWarning() << "Failed to parse pack.png."; - return false; - } - return true; -} - -bool processPackPNG(const ResourcePack* pack) -{ - auto png_invalid = [&pack]() { - qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png"; - return false; - }; - - switch (pack->type()) { - case ResourceType::FOLDER: { - QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png")); - if (image_file_info.exists() && image_file_info.isFile()) { - QFile pack_png_file(image_file_info.filePath()); - if (!pack_png_file.open(QIODevice::ReadOnly)) - return png_invalid(); // can't open pack.png file - - auto data = pack_png_file.readAll(); - - bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data)); - - pack_png_file.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - return png_invalid(); // pack.png does not exists or is not a valid file. - } - return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 - } - case ResourceType::ZIPFILE: { - QuaZip zip(pack->fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file - - QuaZipFile file(&zip); - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } - - auto data = file.readAll(); - - bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - return png_invalid(); // could not set pack.mcmeta as current file. - } - return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 - } - default: - qWarning() << "Invalid type for resource pack parse task!"; - return false; - } -} - -bool validate(QFileInfo file) -{ - ResourcePack rp{ file }; - return DataPackUtils::process(&rp, DataPackUtils::ProcessingLevel::BasicInfoOnly) && rp.valid(); -} - -} // namespace ResourcePackUtils diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h deleted file mode 100644 index 6b4378aa6..000000000 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2022 flowln - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include -#include - -#include "minecraft/mod/ResourcePack.h" - -namespace ResourcePackUtils { - -QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false); -bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data); - -/// processes ONLY the pack.png (rest of the pack may be invalid) -bool processPackPNG(const ResourcePack* pack); - -/** Checks whether a file is valid as a resource pack or not. */ -bool validate(QFileInfo file); -} // namespace ResourcePackUtils diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index 4cc30411e..b53c870f7 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -36,14 +36,14 @@ */ #include "MinecraftSettingsWidget.h" -#include +#include "ui_MinecraftSettingsWidget.h" +#include #include "Application.h" #include "BuildConfig.h" #include "minecraft/WorldList.h" #include "minecraft/auth/AccountList.h" #include "settings/Setting.h" -#include "ui_MinecraftSettingsWidget.h" MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, QWidget* parent) : QWidget(parent), m_instance(std::move(instance)), m_ui(new Ui::MinecraftSettingsWidget) diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp index 9979a9fa6..e8c1d3226 100644 --- a/tests/MetaComponentParse_test.cpp +++ b/tests/MetaComponentParse_test.cpp @@ -41,6 +41,7 @@ #include +#include #include class MetaComponentParseTest : public QObject { @@ -69,7 +70,7 @@ class MetaComponentParseTest : public QObject { QString expected = expected_json.toString(); - QString processed = ResourcePackUtils::processComponent(description_json); + QString processed = DataPackUtils::processComponent(description_json); QCOMPARE(processed, expected); } From 9c920fbad9ebdbc00cab4ed05740916d537d9d88 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 01:10:10 +0000 Subject: [PATCH 10/22] Open FileDialog in game root Signed-off-by: TheKodeToad --- launcher/ui/widgets/MinecraftSettingsWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index b53c870f7..79ffccce8 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -477,7 +477,7 @@ bool MinecraftSettingsWidget::isQuickPlaySupported() void MinecraftSettingsWidget::selectDataPacksFolder() { - QString path = QFileDialog::getExistingDirectory(this, tr("Select Global Data Packs Folder"), m_instance->instanceRoot()); + QString path = QFileDialog::getExistingDirectory(this, tr("Select Global Data Packs Folder"), m_instance->gameRoot()); if (path.isEmpty()) return; From 683df62980c91b72ca562bf823fa71636f2c5c1d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 01:30:06 +0000 Subject: [PATCH 11/22] Implement data pack updating Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/DataPackPage.cpp | 183 +++++++++++++++++++- launcher/ui/pages/instance/DataPackPage.h | 7 + 2 files changed, 184 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index f99d618b0..b9f23bac0 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -17,28 +17,46 @@ */ #include "DataPackPage.h" +#include #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" DataPackPage::DataPackPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) - : ExternalResourcesPage(instance, model, parent) + : ExternalResourcesPage(instance, model, parent), m_model(model) { - ui->actionDownloadItem->setText(tr("Download packs")); - ui->actionDownloadItem->setToolTip(tr("Download data packs from online platforms")); + ui->actionDownloadItem->setText(tr("Download Packs")); + ui->actionDownloadItem->setToolTip(tr("Download data packs from online mod platforms")); ui->actionDownloadItem->setEnabled(true); - connect(ui->actionDownloadItem, &QAction::triggered, this, &DataPackPage::downloadDataPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem); - ui->actionViewConfigs->setVisible(false); + connect(ui->actionDownloadItem, &QAction::triggered, this, &DataPackPage::downloadDataPacks); + + ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected data packs (all data packs if none are selected)")); + connect(ui->actionUpdateItem, &QAction::triggered, this, &DataPackPage::updateDataPacks); + ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); + + auto updateMenu = new QMenu(this); + + auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + connect(update, &QAction::triggered, this, &DataPackPage::updateDataPacks); + + updateMenu->addAction(ui->actionResetItemMetadata); + connect(ui->actionResetItemMetadata, &QAction::triggered, this, &DataPackPage::deleteDataPackMetadata); + + ui->actionUpdateItem->setMenu(updateMenu); + + ui->actionChangeVersion->setToolTip(tr("Change a data pack's version.")); + connect(ui->actionChangeVersion, &QAction::triggered, this, &DataPackPage::changeDataPackVersion); + ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion); } void DataPackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& dp = static_cast(m_model->at(row)); + auto& dp = m_model->at(row); ui->frame->updateWithDataPack(dp); } @@ -78,6 +96,159 @@ void DataPackPage::downloadDataPacks() } } + +void DataPackPage::updateDataPacks() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); + return; + } + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = CustomMessageBox::selectable( + this, tr("Confirm Update"), + tr("Updating data packs while the game is running may cause pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + + auto mods_list = m_model->selectedResources(selection); + bool use_all = mods_list.empty(); + if (use_all) + mods_list = m_model->allResources(); + + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + update_dialog.checkCandidates(); + + if (update_dialog.aborted()) { + CustomMessageBox::selectable(this, tr("Aborted"), tr("The data pack updater was aborted!"), QMessageBox::Warning)->show(); + return; + } + if (update_dialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; + if (mods_list.size() > 1) { + if (use_all) { + message = tr("All data packs are up-to-date! :)"); + } else { + message = tr("All selected data packs are up-to-date! :)"); + } + } + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); + return; + } + + if (update_dialog.exec()) { + auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + tasks->deleteLater(); + }); + + for (auto task : update_dialog.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + +void DataPackPage::deleteDataPackMetadata() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto selectionCount = m_model->selectedDataPacks(selection).length(); + if (selectionCount == 0) + return; + if (selectionCount > 1) { + auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), + tr("You are about to remove the metadata for %1 data packs.\n" + "Are you sure?") + .arg(selectionCount), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } + + m_model->deleteMetadata(selection); +} + +void DataPackPage::changeDataPackVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); + return; + } + + const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); + + if (rows.count() != 1) + return; + + Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); + + if (resource.metadata() == nullptr) + return; + + ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setResourceMetadata(resource.metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} + GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent) : QWidget(parent), m_instance(instance) { auto layout = new QVBoxLayout(this); diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h index 781dd1092..80eda1602 100644 --- a/launcher/ui/pages/instance/DataPackPage.h +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -36,6 +36,13 @@ class DataPackPage : public ExternalResourcesPage { public slots: void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; void downloadDataPacks(); + void updateDataPacks(); + void deleteDataPackMetadata(); + void changeDataPackVersion(); + + private: + std::shared_ptr m_model; + }; /** From 7bac7f7b2b6aa906fd09b831cb052475fc15f6ea Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 01:32:17 +0000 Subject: [PATCH 12/22] Actually fix build? Signed-off-by: TheKodeToad --- tests/MetaComponentParse_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/MetaComponentParse_test.cpp b/tests/MetaComponentParse_test.cpp index e8c1d3226..c5c41388b 100644 --- a/tests/MetaComponentParse_test.cpp +++ b/tests/MetaComponentParse_test.cpp @@ -42,7 +42,6 @@ #include #include -#include class MetaComponentParseTest : public QObject { Q_OBJECT From 36ceaebcf0b6f74e5e8fc4d286ad6dbbebdbfa26 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 01:50:05 +0000 Subject: [PATCH 13/22] pls compile Signed-off-by: TheKodeToad --- tests/ResourcePackParse_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp index 17c0482fc..e9b5244ad 100644 --- a/tests/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -23,7 +23,6 @@ #include #include -#include class ResourcePackParseTest : public QObject { Q_OBJECT From 221365f05b9d816c49721789edd10fc1d3d1f6f0 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 10:12:13 +0000 Subject: [PATCH 14/22] Make requested changes Signed-off-by: TheKodeToad --- launcher/minecraft/mod/DataPackFolderModel.cpp | 13 ++++++------- launcher/ui/pages/instance/ExternalResourcesPage.h | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index c94f61fc2..45cf1271f 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -47,7 +47,8 @@ #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); @@ -123,12 +124,10 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const } return {}; case Qt::CheckStateRole: - switch (column) { - case ActiveColumn: - return at(row).enabled() ? Qt::Checked : Qt::Unchecked; - default: - return {}; - } + if (column == ActiveColumn) + return at(row).enabled() ? Qt::Checked : Qt::Unchecked; + else + return {}; default: return {}; } diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index d9077d7e6..00bb5d17d 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include From 1d1480f4706d6ae11cfc34ce92ba735891faa10b Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 13:24:10 +0000 Subject: [PATCH 15/22] Filter for datapack loader in datapack update too Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 15 ++++----------- launcher/ui/dialogs/ResourceUpdateDialog.h | 4 ++-- launcher/ui/pages/instance/DataPackPage.cpp | 17 ++++++++--------- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- launcher/ui/pages/instance/ResourcePackPage.cpp | 2 +- launcher/ui/pages/instance/ShaderPackPage.cpp | 2 +- launcher/ui/pages/instance/TexturePackPage.cpp | 2 +- 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 7e29e1192..f2b64a60f 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -32,17 +32,12 @@ static std::list mcVersions(BaseInstance* inst) return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } -static QList mcLoadersList(BaseInstance* inst) -{ - return static_cast(inst)->getPackProfile()->getModLoadersList(); -} - ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, const std::shared_ptr resource_model, QList& search_for, bool include_deps, - bool filter_loaders) + QList loadersList) : ReviewMessageBox(parent, tr("Confirm resources to update"), "") , m_parent(parent) , m_resource_model(resource_model) @@ -50,7 +45,7 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, , m_second_try_metadata(new ConcurrentTask("Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())) , m_instance(instance) , m_include_deps(include_deps) - , m_filter_loaders(filter_loaders) + , m_loadersList(std::move(loadersList)) { ReviewMessageBox::setGeometry(0, 0, 800, 600); @@ -89,12 +84,10 @@ void ResourceUpdateDialog::checkCandidates() } auto versions = mcVersions(m_instance); - auto loadersList = m_filter_loaders ? mcLoadersList(m_instance) : QList(); - SequentialTask check_task(tr("Checking for updates")); if (!m_modrinth_to_update.empty()) { - m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loadersList, m_resource_model)); + m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, m_loadersList, m_resource_model)); connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { m_failed_check_update.append({ resource, reason, recover_url }); @@ -103,7 +96,7 @@ void ResourceUpdateDialog::checkCandidates() } if (!m_flame_to_update.empty()) { - m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loadersList, m_resource_model)); + m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, m_loadersList, m_resource_model)); connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { m_failed_check_update.append({ resource, reason, recover_url }); diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index de1d845d2..aef11c90f 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -21,7 +21,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { std::shared_ptr resource_model, QList& search_for, bool include_deps, - bool filter_loaders); + QList loadersList = {}); void checkCandidates(); @@ -64,5 +64,5 @@ class ResourceUpdateDialog final : public ReviewMessageBox { bool m_no_updates = false; bool m_aborted = false; bool m_include_deps = false; - bool m_filter_loaders = false; + QList m_loadersList; }; diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index b9f23bac0..1723bc37f 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -96,7 +96,6 @@ void DataPackPage::downloadDataPacks() } } - void DataPackPage::updateDataPacks() { if (m_instance->typeName() != "Minecraft") @@ -108,13 +107,13 @@ void DataPackPage::updateDataPacks() return; } if (m_instance != nullptr && m_instance->isRunning()) { - auto response = CustomMessageBox::selectable( - this, tr("Confirm Update"), - tr("Updating data packs while the game is running may cause pack duplication and game crashes.\n" - "The old files may not be deleted as they are in use.\n" - "Are you sure you want to do this?"), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); + auto response = + CustomMessageBox::selectable(this, tr("Confirm Update"), + tr("Updating data packs while the game is running may cause pack duplication and game crashes.\n" + "The old files may not be deleted as they are in use.\n" + "Are you sure you want to do this?"), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); if (response != QMessageBox::Yes) return; @@ -126,7 +125,7 @@ void DataPackPage::updateDataPacks() if (use_all) mods_list = m_model->allResources(); - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, { ModPlatform::ModLoaderType::DataPack }); update_dialog.checkCandidates(); if (update_dialog.aborted()) { diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 95507ac22..a4dc45942 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -210,7 +210,7 @@ void ModFolderPage::updateMods(bool includeDeps) if (use_all) mods_list = m_model->allResources(); - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, true); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, profile->getModLoadersList()); update_dialog.checkCandidates(); if (update_dialog.aborted()) { diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 79e677765..f9fbc6176 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -144,7 +144,7 @@ void ResourcePackPage::updateResourcePacks() if (use_all) mods_list = m_model->allResources(); - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); update_dialog.checkCandidates(); if (update_dialog.aborted()) { diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index a287d3edf..00a17bfdf 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -141,7 +141,7 @@ void ShaderPackPage::updateShaderPacks() if (use_all) mods_list = m_model->allResources(); - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); update_dialog.checkCandidates(); if (update_dialog.aborted()) { diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index fd1e0a2fc..ed74c90da 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -150,7 +150,7 @@ void TexturePackPage::updateTexturePacks() if (use_all) mods_list = m_model->allResources(); - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false); + ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); update_dialog.checkCandidates(); if (update_dialog.aborted()) { From 5ece4bae703e5dd6899077682b6edb25fde3dc5e Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 16:11:53 +0000 Subject: [PATCH 16/22] Add CurseForge support Currently doesn't work. Will try another approach to modrinth filter. Signed-off-by: TheKodeToad --- launcher/modplatform/flame/FlameAPI.h | 5 ++- .../ui/dialogs/ResourceDownloadDialog.cpp | 9 +++-- .../modplatform/flame/FlameResourceModels.cpp | 28 +++++++++++++ .../modplatform/flame/FlameResourceModels.h | 19 +++++++++ .../modplatform/flame/FlameResourcePages.cpp | 39 +++++++++++++++++++ .../modplatform/flame/FlameResourcePages.h | 28 +++++++++++++ 6 files changed, 122 insertions(+), 6 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 14e4effec..f9b4c11a5 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -43,12 +43,13 @@ class FlameAPI : public NetworkResourceAPI { case ModPlatform::ResourceType::MOD: return 6; case ModPlatform::ResourceType::RESOURCE_PACK: - case ModPlatform::ResourceType::DATA_PACK: return 12; case ModPlatform::ResourceType::SHADER_PACK: return 6552; case ModPlatform::ResourceType::MODPACK: return 4471; + case ModPlatform::ResourceType::DATA_PACK: + return 6945; } } @@ -69,7 +70,7 @@ class FlameAPI : public NetworkResourceAPI { case ModPlatform::NeoForge: return 6; case ModPlatform::DataPack: - break; // not supported + break; // not supported } return 0; } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index a9fa826ec..fe9ee7bdb 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -397,10 +397,9 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptropenProject(meta->project_id); } - DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, - const std::shared_ptr& data_packs, - BaseInstance* instance) + const std::shared_ptr& data_packs, + BaseInstance* instance) : ResourceDownloadDialog(parent, data_packs), m_instance(instance) { setWindowTitle(dialogTitle()); @@ -416,7 +415,9 @@ QList DataPackDownloadDialog::getPages() { QList pages; pages.append(ModrinthDataPackPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame) + pages.append(FlameDataPackPage::create(this, *m_instance)); return pages; } - + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index fea1fc27a..5b254a675 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -170,4 +170,32 @@ auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArr return Json::ensureArray(obj.object(), "data"); } +FlameDataPackModel::FlameDataPackModel(const BaseInstance& base) : DataPackResourceModel(base, new FlameAPI) {} + +void FlameDataPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadIndexedPack(m, obj); +} + +// We already deal with the URLs when initializing the pack, due to the API response's structure +void FlameDataPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + +void FlameDataPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + FlameMod::loadIndexedPackVersions(m, arr); +} + +bool FlameDataPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const +{ + return isOptedOut(ver); +} + +auto FlameDataPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return Json::ensureArray(obj.object(), "data"); +} + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 458fd85d0..b80217a16 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -93,4 +93,23 @@ class FlameShaderPackModel : public ShaderPackResourceModel { auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; }; +class FlameDataPackModel : public DataPackResourceModel { + Q_OBJECT + + public: + FlameDataPackModel(const BaseInstance&); + ~FlameDataPackModel() override = default; + + bool optedOut(const ModPlatform::IndexedVersion& ver) const override; + + private: + [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } + [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; +}; + } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 4e01f3a65..609d77608 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -152,6 +152,22 @@ void FlameTexturePackPage::openUrl(const QUrl& url) TexturePackResourcePage::openUrl(url); } +void FlameDataPackPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + DataPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) }); // double decoding is necessary + return; + } + } + + DataPackResourcePage::openUrl(url); +} + FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ShaderPackResourcePage(dialog, instance) { @@ -171,6 +187,25 @@ FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseI m_ui->packDescription->setMetaEntry(metaEntryBase()); } +FlameDataPackPage::FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance) + : DataPackResourcePage(dialog, instance) +{ + m_model = new FlameDataPackModel(instance); + m_ui->packView->setModel(m_model); + + addSortings(); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameDataPackPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &FlameDataPackPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameDataPackPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + void FlameShaderPackPage::openUrl(const QUrl& url) { if (url.scheme().isEmpty()) { @@ -206,6 +241,10 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool { return true; } +auto FlameDataPackPage::shouldDisplay() const -> bool +{ + return true; +} unique_qobject_ptr FlameModPage::createFilterWidget() { diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 052706549..306cdb4f3 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -39,6 +39,7 @@ #pragma once +#include #include "Application.h" #include "modplatform/ResourceAPI.h" @@ -180,4 +181,31 @@ class FlameShaderPackPage : public ShaderPackResourcePage { void openUrl(const QUrl& url) override; }; + + +class FlameDataPackPage : public DataPackResourcePage { + Q_OBJECT + + public: + static FlameDataPackPage* create(DataPackDownloadDialog* dialog, BaseInstance& instance) + { + return DataPackResourcePage::create(dialog, instance); + } + + FlameDataPackPage(DataPackDownloadDialog* dialog, BaseInstance& instance); + ~FlameDataPackPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } + [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } + [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } + [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + + [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + + void openUrl(const QUrl& url) override; +}; + } // namespace ResourceDownload From 9c942c68943afa47512a01d1c229c2bbdf7925b3 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 23:35:40 +0000 Subject: [PATCH 17/22] Fix CurseForge support Signed-off-by: TheKodeToad --- launcher/modplatform/flame/FlameAPI.h | 10 +++++++--- launcher/modplatform/import_ftb/PackInstallTask.cpp | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index f9b4c11a5..db96b6971 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -100,8 +100,12 @@ class FlameAPI : public NetworkResourceAPI { if (args.sorting.has_value()) get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); get_arguments.append("sortOrder=desc"); - if (args.loaders.has_value() && args.loaders.value() != 0) - get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value()))); + if (args.loaders.has_value()) { + ModPlatform::ModLoaderTypes loaders = args.loaders.value(); + loaders &= ~ModPlatform::ModLoaderType::DataPack; + if (loaders != 0) + get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(loaders))); + } if (args.categoryIds.has_value() && !args.categoryIds->empty()) get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(","))); @@ -119,7 +123,7 @@ class FlameAPI : public NetworkResourceAPI { if (args.mcVersions.has_value()) url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString()); - if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) { + if (args.loaders.has_value() && args.loaders.value() != ModPlatform::ModLoaderType::DataPack && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) { int mappedModLoader = getMappedModLoader(static_cast(static_cast(args.loaders.value()))); url += QString("&modLoaderType=%1").arg(mappedModLoader); } diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp index 8046300e1..7cb8b6ebc 100644 --- a/launcher/modplatform/import_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -89,6 +89,8 @@ void PackInstallTask::copySettings() break; case ModPlatform::LiteLoader: break; + case ModPlatform::DataPack: + break; } components->saveNow(); From ccef855f06614a51292741ce8875de1bf9f4b538 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 23:41:47 +0000 Subject: [PATCH 18/22] Reset path when unchecked (still keeps it in UI in case you recheck) Signed-off-by: TheKodeToad --- launcher/ui/widgets/MinecraftSettingsWidget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index 79ffccce8..f369e393c 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -101,8 +101,11 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, connect(m_ui->serverJoinAddressButton, &QAbstractButton::toggled, m_ui->serverJoinAddress, &QWidget::setEnabled); connect(m_ui->worldJoinButton, &QAbstractButton::toggled, m_ui->worldsCb, &QWidget::setEnabled); - connect(m_ui->globalDataPacksGroupBox, &QGroupBox::toggled, this, - [this](bool value) { m_instance->settings()->set("GlobalDataPacksEnabled", value); }); + connect(m_ui->globalDataPacksGroupBox, &QGroupBox::toggled, this, [this](bool value) { + m_instance->settings()->set("GlobalDataPacksEnabled", value); + if (!value) + m_instance->settings()->reset("GlobalDataPacksPath"); + }); connect(m_ui->dataPacksPathEdit, &QLineEdit::editingFinished, this, [this] { m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text()); }); connect(m_ui->dataPacksPathBrowse, &QPushButton::clicked, this, &MinecraftSettingsWidget::selectDataPacksFolder); From 1e7ceafa5f318da0ec997d8c40b4b97fdc8bc816 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Mar 2025 23:48:00 +0000 Subject: [PATCH 19/22] Auto-fix \ to / on Windows (for portability) Signed-off-by: TheKodeToad --- launcher/ui/widgets/MinecraftSettingsWidget.cpp | 11 +++++++++-- launcher/ui/widgets/MinecraftSettingsWidget.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index f369e393c..df7ed083b 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -106,8 +106,7 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstancePtr instance, if (!value) m_instance->settings()->reset("GlobalDataPacksPath"); }); - connect(m_ui->dataPacksPathEdit, &QLineEdit::editingFinished, this, - [this] { m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text()); }); + connect(m_ui->dataPacksPathEdit, &QLineEdit::editingFinished, this, &MinecraftSettingsWidget::editedDataPacksPath); connect(m_ui->dataPacksPathBrowse, &QPushButton::clicked, this, &MinecraftSettingsWidget::selectDataPacksFolder); } @@ -478,6 +477,14 @@ bool MinecraftSettingsWidget::isQuickPlaySupported() return m_instance->traits().contains("feature:is_quick_play_singleplayer"); } +void MinecraftSettingsWidget::editedDataPacksPath() +{ + if (QDir::separator() != '/') + m_ui->dataPacksPathEdit->setText(m_ui->dataPacksPathEdit->text().replace(QDir::separator(), '/')); + + m_instance->settings()->set("GlobalDataPacksPath", m_ui->dataPacksPathEdit->text()); +} + void MinecraftSettingsWidget::selectDataPacksFolder() { QString path = QFileDialog::getExistingDirectory(this, tr("Select Global Data Packs Folder"), m_instance->gameRoot()); diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.h b/launcher/ui/widgets/MinecraftSettingsWidget.h index 0f9e35b9c..002cd2d56 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.h +++ b/launcher/ui/widgets/MinecraftSettingsWidget.h @@ -56,6 +56,7 @@ class MinecraftSettingsWidget : public QWidget { void openGlobalSettings(); void updateAccountsMenu(const SettingsObject& settings); bool isQuickPlaySupported(); + void editedDataPacksPath(); void selectDataPacksFolder(); MinecraftInstancePtr m_instance; From 80fb1a8f4ea67d32b401378c3a5112bee62720de Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 29 Apr 2025 12:15:58 +0100 Subject: [PATCH 20/22] Fixes for new changes Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/DataPackPage.cpp | 34 +++++++++++++++---- launcher/ui/pages/instance/DataPackPage.h | 8 +++-- .../ui/pages/modplatform/DataPackModel.cpp | 4 +-- launcher/ui/pages/modplatform/DataPackModel.h | 4 +-- .../ui/pages/modplatform/DataPackPage.cpp | 6 +--- launcher/ui/pages/modplatform/DataPackPage.h | 5 +-- 6 files changed, 41 insertions(+), 20 deletions(-) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index 1723bc37f..2fc4ec31d 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -17,11 +17,13 @@ */ #include "DataPackPage.h" -#include +#include "minecraft/PackProfile.h" +#include "ui_ExternalResourcesPage.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/dialogs/ResourceUpdateDialog.h" DataPackPage::DataPackPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(instance, model, parent), m_model(model) @@ -65,9 +67,23 @@ void DataPackPage::downloadDataPacks() if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance - ResourceDownload::DataPackDownloadDialog mdownload(this, std::static_pointer_cast(m_model), m_instance); - if (mdownload.exec()) { - auto tasks = new ConcurrentTask("Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + auto profile = static_cast(m_instance)->getPackProfile(); + if (!profile->getModLoaders().has_value()) { + QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + return; + } + + m_downloadDialog = new ResourceDownload::DataPackDownloadDialog(this, m_model, m_instance); + connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); + connect(m_downloadDialog, &QDialog::finished, this, &DataPackPage::downloadDialogFinished); + + m_downloadDialog->open(); +} + +void DataPackPage::downloadDialogFinished(int result) +{ + if (result) { + auto tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); connect(tasks, &Task::failed, [this, tasks](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); @@ -84,8 +100,12 @@ void DataPackPage::downloadDataPacks() tasks->deleteLater(); }); - for (auto& task : mdownload.getTasks()) { - tasks->addTask(task); + if (m_downloadDialog) { + for (auto& task : m_downloadDialog->getTasks()) { + tasks->addTask(task); + } + } else { + qWarning() << "ResourceDownloadDialog vanished before we could collect tasks!"; } ProgressDialog loadDialog(this); @@ -94,6 +114,8 @@ void DataPackPage::downloadDataPacks() m_model->update(); } + if (m_downloadDialog) + m_downloadDialog->deleteLater(); } void DataPackPage::updateDataPacks() diff --git a/launcher/ui/pages/instance/DataPackPage.h b/launcher/ui/pages/instance/DataPackPage.h index 80eda1602..6676c165a 100644 --- a/launcher/ui/pages/instance/DataPackPage.h +++ b/launcher/ui/pages/instance/DataPackPage.h @@ -18,9 +18,10 @@ #pragma once +#include #include "ExternalResourcesPage.h" #include "minecraft/mod/DataPackFolderModel.h" -#include "ui_ExternalResourcesPage.h" +#include "ui/dialogs/ResourceDownloadDialog.h" class DataPackPage : public ExternalResourcesPage { Q_OBJECT @@ -36,13 +37,14 @@ class DataPackPage : public ExternalResourcesPage { public slots: void updateFrame(const QModelIndex& current, const QModelIndex& previous) override; void downloadDataPacks(); + void downloadDialogFinished(int result); void updateDataPacks(); void deleteDataPackMetadata(); void changeDataPackVersion(); private: std::shared_ptr m_model; - + QPointer m_downloadDialog; }; /** @@ -63,7 +65,7 @@ class GlobalDataPackPage : public QWidget, public BasePage { void openedImpl() override; void closedImpl() override; - void setParentContainer(BasePageContainer *container) override; + void setParentContainer(BasePageContainer* container) override; private: void updateContent(); diff --git a/launcher/ui/pages/modplatform/DataPackModel.cpp b/launcher/ui/pages/modplatform/DataPackModel.cpp index 4b537cda9..085bd2d53 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.cpp +++ b/launcher/ui/pages/modplatform/DataPackModel.cpp @@ -21,13 +21,13 @@ ResourceAPI::SearchArgs DataPackResourceModel::createSearchArguments() return { ModPlatform::ResourceType::DATA_PACK, m_next_search_offset, m_search_term, sort, ModPlatform::ModLoaderType::DataPack }; } -ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(QModelIndex& entry) +ResourceAPI::VersionSearchArgs DataPackResourceModel::createVersionsArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack, {}, ModPlatform::ModLoaderType::DataPack }; } -ResourceAPI::ProjectInfoArgs DataPackResourceModel::createInfoArguments(QModelIndex& entry) +ResourceAPI::ProjectInfoArgs DataPackResourceModel::createInfoArguments(const QModelIndex& entry) { auto& pack = m_packs[entry.row()]; return { *pack }; diff --git a/launcher/ui/pages/modplatform/DataPackModel.h b/launcher/ui/pages/modplatform/DataPackModel.h index 54da2404c..89e83969c 100644 --- a/launcher/ui/pages/modplatform/DataPackModel.h +++ b/launcher/ui/pages/modplatform/DataPackModel.h @@ -32,8 +32,8 @@ class DataPackResourceModel : public ResourceModel { public slots: ResourceAPI::SearchArgs createSearchArguments() override; - ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; - ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::VersionSearchArgs createVersionsArguments(const QModelIndex&) override; + ResourceAPI::ProjectInfoArgs createInfoArguments(const QModelIndex&) override; protected: const BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/DataPackPage.cpp b/launcher/ui/pages/modplatform/DataPackPage.cpp index 2b506ca67..82892b318 100644 --- a/launcher/ui/pages/modplatform/DataPackPage.cpp +++ b/launcher/ui/pages/modplatform/DataPackPage.cpp @@ -4,7 +4,6 @@ // SPDX-License-Identifier: GPL-3.0-only #include "DataPackPage.h" -#include "modplatform/ModIndex.h" #include "ui_ResourcePage.h" #include "DataPackModel.h" @@ -15,10 +14,7 @@ namespace ResourceDownload { -DataPackResourcePage::DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) -{ - connect(m_ui->packView, &QListView::doubleClicked, this, &DataPackResourcePage::onResourceSelected); -} +DataPackResourcePage::DataPackResourcePage(DataPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance) {} /******** Callbacks to events in the UI (set up in the derived classes) ********/ diff --git a/launcher/ui/pages/modplatform/DataPackPage.h b/launcher/ui/pages/modplatform/DataPackPage.h index 2e622ebd4..cf78df96c 100644 --- a/launcher/ui/pages/modplatform/DataPackPage.h +++ b/launcher/ui/pages/modplatform/DataPackPage.h @@ -5,8 +5,8 @@ #pragma once -#include "ui/pages/modplatform/ResourcePage.h" #include "ui/pages/modplatform/DataPackModel.h" +#include "ui/pages/modplatform/ResourcePage.h" namespace Ui { class ResourcePage; @@ -26,8 +26,9 @@ class DataPackResourcePage : public ResourcePage { auto page = new T(dialog, instance); auto model = static_cast(page->getModel()); - connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList); + connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); + connect(model, &QAbstractListModel::modelReset, page, &ResourcePage::modelReset); return page; } From b54dd051fbbd2f06106420a28c132ab26a16b774 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 1 Jun 2025 19:24:21 +0000 Subject: [PATCH 21/22] Fix close button on world datapacks dialog Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/WorldListPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index de71eb8fb..6c10413e4 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -255,7 +255,7 @@ void WorldListPage::on_actionData_Packs_triggered() layout->addWidget(pageContainer); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::Help); - connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::helpRequested, pageContainer, &PageContainer::help); layout->addWidget(buttonBox); From af0176b12a53209d45351aa0a6237d34f2e5cd77 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 2 Jun 2025 09:34:23 +0100 Subject: [PATCH 22/22] Allow data packs on vanilla instances Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/DataPackPage.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index 2fc4ec31d..a56cc9b79 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -68,10 +68,6 @@ void DataPackPage::downloadDataPacks() return; // this is a null instance or a legacy instance auto profile = static_cast(m_instance)->getPackProfile(); - if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); - return; - } m_downloadDialog = new ResourceDownload::DataPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close);