Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into packwiz
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
commit
9445a555e4
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Create backport PRs
|
||||
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -125,7 +125,7 @@ jobs:
|
||||
# PREPARE
|
||||
##
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
@ -164,7 +164,7 @@ jobs:
|
||||
|
||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
uses: actions/cache@v3.3.1
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
@ -620,7 +620,7 @@ jobs:
|
||||
options: --privileged
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
if: inputs.build_type == 'Debug'
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -8,7 +8,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
|
2
.github/workflows/trigger_release.yml
vendored
2
.github/workflows/trigger_release.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
path: 'PrismLauncher-source'
|
||||
|
6
.github/workflows/update-flake.yml
vendored
6
.github/workflows/update-flake.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v19
|
||||
- uses: DeterminateSystems/update-flake-lock@v20
|
||||
with:
|
||||
commit-msg: "chore(nix): update lockfile"
|
||||
pr-title: "chore(nix): update lockfile"
|
||||
|
@ -75,7 +75,6 @@ function(
|
||||
set(CLANG_WARNINGS
|
||||
-Wall
|
||||
-Wextra # reasonable and standard
|
||||
-Wextra-semi # Warn about semicolon after in-class function definition.
|
||||
-Wshadow # warn the user if a variable declaration shadows one from a parent context
|
||||
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps
|
||||
# catch hard to track down memory errors
|
||||
|
24
flake.lock
generated
24
flake.lock
generated
@ -21,11 +21,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1690933134,
|
||||
"narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
|
||||
"lastModified": 1693611461,
|
||||
"narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
|
||||
"rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -91,11 +91,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1691853136,
|
||||
"narHash": "sha256-wTzDsRV4HN8A2Sl0SVQY0q8ILs90CD43Ha//7gNZE+E=",
|
||||
"lastModified": 1693626178,
|
||||
"narHash": "sha256-Rpiy6lIOu4zny8tfGuIeN1ji9eSz9nPmm9yBhh/4IOM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f0451844bbdf545f696f029d1448de4906c7f753",
|
||||
"rev": "bfb7dfec93f3b5d7274db109f2990bc889861caf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -108,11 +108,11 @@
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1690881714,
|
||||
"narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
|
||||
"lastModified": 1693471703,
|
||||
"narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9e1960bc196baf6881340d53dccb203a951745a2",
|
||||
"rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -138,11 +138,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1691747570,
|
||||
"narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=",
|
||||
"lastModified": 1692274144,
|
||||
"narHash": "sha256-BxTQuRUANQ81u8DJznQyPmRsg63t4Yc+0kcyq6OLz8s=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a",
|
||||
"rev": "7e3517c03d46159fdbf8c0e5c97f82d5d4b0c8fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
16
flake.nix
16
flake.nix
@ -23,5 +23,19 @@
|
||||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake
|
||||
{inherit inputs;}
|
||||
{imports = [./nix];};
|
||||
{
|
||||
imports = [
|
||||
inputs.pre-commit-hooks.flakeModule
|
||||
|
||||
./nix/dev.nix
|
||||
./nix/distribution.nix
|
||||
];
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
* Copyright (C) 2023 seth <getchoo at tuta dot io>
|
||||
*
|
||||
* 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
|
||||
@ -580,9 +579,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
||||
m_settings->registerSetting("IgnoreJavaWizard", false);
|
||||
|
||||
// Mod loader settings
|
||||
m_settings->registerSetting("DisableQuiltBeacon", false);
|
||||
|
||||
// Native library workarounds
|
||||
m_settings->registerSetting("UseNativeOpenAL", false);
|
||||
m_settings->registerSetting("CustomOpenALPath", "");
|
||||
@ -598,6 +594,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("ShowGameTime", true);
|
||||
m_settings->registerSetting("ShowGlobalGameTime", true);
|
||||
m_settings->registerSetting("RecordGameTime", true);
|
||||
m_settings->registerSetting("ShowGameTimeWithoutDays", false);
|
||||
|
||||
// Minecraft mods
|
||||
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||
@ -968,7 +965,7 @@ void Application::performMainStartupAction()
|
||||
qDebug() << " Launching with account" << m_profileToUse;
|
||||
}
|
||||
|
||||
launch(inst, true, false, nullptr, serverToJoin, accountToUse);
|
||||
launch(inst, true, false, serverToJoin, accountToUse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1067,7 +1064,7 @@ void Application::messageReceived(const QByteArray& message)
|
||||
}
|
||||
}
|
||||
|
||||
launch(instance, true, false, nullptr, serverObject, accountObject);
|
||||
launch(instance, true, false, serverObject, accountObject);
|
||||
} else {
|
||||
qWarning() << "Received invalid message" << message;
|
||||
}
|
||||
@ -1108,7 +1105,6 @@ bool Application::openJsonEditor(const QString& filename)
|
||||
bool Application::launch(InstancePtr instance,
|
||||
bool online,
|
||||
bool demo,
|
||||
BaseProfilerFactory* profiler,
|
||||
MinecraftServerTargetPtr serverToJoin,
|
||||
MinecraftAccountPtr accountToUse)
|
||||
{
|
||||
@ -1116,7 +1112,7 @@ bool Application::launch(InstancePtr instance,
|
||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||
} else if (instance->canLaunch()) {
|
||||
auto& extras = m_instanceExtras[instance->id()];
|
||||
auto& window = extras.window;
|
||||
auto window = extras.window;
|
||||
if (window) {
|
||||
if (!window->saveAll()) {
|
||||
return false;
|
||||
@ -1127,7 +1123,7 @@ bool Application::launch(InstancePtr instance,
|
||||
controller->setInstance(instance);
|
||||
controller->setOnline(online);
|
||||
controller->setDemo(demo);
|
||||
controller->setProfiler(profiler);
|
||||
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
|
||||
controller->setServerToJoin(serverToJoin);
|
||||
controller->setAccountToUse(accountToUse);
|
||||
if (window) {
|
||||
|
@ -195,7 +195,6 @@ class Application : public QApplication {
|
||||
bool launch(InstancePtr instance,
|
||||
bool online = true,
|
||||
bool demo = false,
|
||||
BaseProfilerFactory* profiler = nullptr,
|
||||
MinecraftServerTargetPtr serverToJoin = nullptr,
|
||||
MinecraftAccountPtr accountToUse = nullptr);
|
||||
bool kill(InstancePtr instance);
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -100,6 +101,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
|
||||
m_settings->registerSetting("ManagedPackName", "");
|
||||
m_settings->registerSetting("ManagedPackVersionID", "");
|
||||
m_settings->registerSetting("ManagedPackVersionName", "");
|
||||
|
||||
m_settings->registerSetting("Profiler", "");
|
||||
}
|
||||
|
||||
QString BaseInstance::getPreLaunchCommand()
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -38,6 +39,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QMenu>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QSet>
|
||||
@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
||||
virtual bool canEdit() const = 0;
|
||||
virtual bool canExport() const = 0;
|
||||
|
||||
virtual void populateLaunchMenu(QMenu* menu) = 0;
|
||||
|
||||
bool reloadSettings();
|
||||
|
||||
/**
|
||||
@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
||||
|
||||
void runningStatusChanged(bool running);
|
||||
|
||||
void profilerChanged();
|
||||
|
||||
void statusChanged(Status from, Status to);
|
||||
|
||||
protected slots:
|
||||
|
@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
|
||||
|
||||
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
||||
{
|
||||
auto fileName = fileInfo.fileName();
|
||||
auto path = relPath(fileInfo.absoluteFilePath());
|
||||
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
|
||||
m_ignoreFilePaths.covers(path);
|
||||
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -361,22 +362,21 @@ void LaunchController::readyForLaunch()
|
||||
QString error;
|
||||
if (!m_profiler->check(&error)) {
|
||||
m_launcher->abort();
|
||||
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
|
||||
emitFailed("Profiler startup failed!");
|
||||
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
|
||||
return;
|
||||
}
|
||||
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
|
||||
|
||||
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) {
|
||||
QMessageBox msg;
|
||||
QMessageBox msg(m_parentWidget);
|
||||
msg.setText(tr("The game launch is delayed until you press the "
|
||||
"button. This is the right time to setup the profiler, as the "
|
||||
"profiler server is running now.\n\n%1")
|
||||
.arg(message));
|
||||
msg.setWindowTitle(tr("Waiting."));
|
||||
msg.setIcon(QMessageBox::Information);
|
||||
msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
|
||||
msg.setModal(true);
|
||||
msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
|
||||
msg.exec();
|
||||
m_launcher->proceed();
|
||||
});
|
||||
|
@ -16,19 +16,20 @@
|
||||
*/
|
||||
|
||||
#include <MMCTime.h>
|
||||
#include <qobject.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QTextStream>
|
||||
|
||||
QString Time::prettifyDuration(int64_t duration)
|
||||
QString Time::prettifyDuration(int64_t duration, bool noDays)
|
||||
{
|
||||
int seconds = (int)(duration % 60);
|
||||
duration /= 60;
|
||||
int minutes = (int)(duration % 60);
|
||||
duration /= 60;
|
||||
int hours = (int)(duration % 24);
|
||||
int days = (int)(duration / 24);
|
||||
int hours = (int)(noDays ? duration : (duration % 24));
|
||||
int days = (int)(noDays ? 0 : (duration / 24));
|
||||
if ((hours == 0) && (days == 0)) {
|
||||
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
namespace Time {
|
||||
|
||||
QString prettifyDuration(int64_t duration);
|
||||
QString prettifyDuration(int64_t duration, bool noDays = false);
|
||||
|
||||
/**
|
||||
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -62,6 +63,7 @@ class NullInstance : public BaseInstance {
|
||||
bool canExport() const override { return false; }
|
||||
bool canEdit() const override { return false; }
|
||||
bool canLaunch() const override { return false; }
|
||||
void populateLaunchMenu(QMenu* menu) override {}
|
||||
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
|
||||
{
|
||||
QStringList out;
|
||||
|
@ -103,14 +103,8 @@ class Version {
|
||||
|
||||
QString m_fullString;
|
||||
|
||||
[[nodiscard]] inline bool isAppendix() const
|
||||
{
|
||||
return m_stringPart.startsWith('+');
|
||||
}
|
||||
[[nodiscard]] inline bool isPreRelease() const
|
||||
{
|
||||
return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
|
||||
}
|
||||
[[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
|
||||
[[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
|
||||
|
||||
inline bool operator==(const Section& other) const
|
||||
{
|
||||
@ -156,14 +150,8 @@ class Version {
|
||||
return m_fullString < other.m_fullString;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Section& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
inline bool operator>(const Section& other) const
|
||||
{
|
||||
return !(*this < other || *this == other);
|
||||
}
|
||||
inline bool operator!=(const Section& other) const { return !(*this == other); }
|
||||
inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -30,7 +30,7 @@ class LogModel : public QAbstractListModel {
|
||||
|
||||
enum Roles { LevelRole = Qt::UserRole };
|
||||
|
||||
private /* types */:
|
||||
private /* types */:
|
||||
struct entry {
|
||||
MessageLevel::Enum level;
|
||||
QString line;
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
#include "Component.h"
|
||||
#include "ComponentUpdateTask_p.h"
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include "PackProfile.h"
|
||||
#include "PackProfile_p.h"
|
||||
#include "Version.h"
|
||||
#include "cassert"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "minecraft/ProfileUtils.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -3,8 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (c) 2023 seth <getchoo at tuta dot io>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -88,6 +87,10 @@
|
||||
#include "minecraft/gameoptions/GameOptions.h"
|
||||
#include "minecraft/update/FoldersTask.h"
|
||||
|
||||
#include "tools/BaseProfiler.h"
|
||||
|
||||
#include <QActionGroup>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "MangoHud.h"
|
||||
#endif
|
||||
@ -181,10 +184,6 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
||||
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
||||
|
||||
// Mod loader specific options
|
||||
auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
|
||||
m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
|
||||
|
||||
m_settings->set("InstanceType", "OneSix");
|
||||
}
|
||||
|
||||
@ -196,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerSetting("UseAccountForInstance", false);
|
||||
m_settings->registerSetting("InstanceAccountId", "");
|
||||
|
||||
m_settings->registerSetting("ExportName", "");
|
||||
m_settings->registerSetting("ExportVersion", "1.0.0");
|
||||
m_settings->registerSetting("ExportSummary", "");
|
||||
m_settings->registerSetting("ExportAuthor", "");
|
||||
m_settings->registerSetting("ExportOptionalFiles", true);
|
||||
|
||||
qDebug() << "Instance-type specific settings were loaded!";
|
||||
|
||||
setSpecificSettingsLoaded(true);
|
||||
@ -231,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const
|
||||
return profile->getTraits();
|
||||
}
|
||||
|
||||
// FIXME: move UI code out of MinecraftInstance
|
||||
void MinecraftInstance::populateLaunchMenu(QMenu* menu)
|
||||
{
|
||||
QAction* normalLaunch = menu->addAction(tr("&Launch"));
|
||||
normalLaunch->setShortcut(QKeySequence::Open);
|
||||
QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline"));
|
||||
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
|
||||
QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo"));
|
||||
normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
|
||||
|
||||
normalLaunchDemo->setEnabled(supportsDemo());
|
||||
|
||||
connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); });
|
||||
connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); });
|
||||
connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); });
|
||||
|
||||
QString profilersTitle = tr("Profilers");
|
||||
menu->addSeparator()->setText(profilersTitle);
|
||||
|
||||
auto profilers = new QActionGroup(menu);
|
||||
profilers->setExclusive(true);
|
||||
connect(profilers, &QActionGroup::triggered, [this](QAction* action) {
|
||||
settings()->set("Profiler", action->data());
|
||||
emit profilerChanged();
|
||||
});
|
||||
|
||||
QAction* noProfilerAction = menu->addAction(tr("&No Profiler"));
|
||||
noProfilerAction->setData("");
|
||||
noProfilerAction->setCheckable(true);
|
||||
noProfilerAction->setChecked(true);
|
||||
profilers->addAction(noProfilerAction);
|
||||
|
||||
for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) {
|
||||
QAction* profilerAction = menu->addAction(profiler.value()->name());
|
||||
profilers->addAction(profilerAction);
|
||||
profilerAction->setData(profiler.key());
|
||||
profilerAction->setCheckable(true);
|
||||
profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key());
|
||||
|
||||
QString error;
|
||||
profilerAction->setEnabled(profiler.value()->check(&error));
|
||||
}
|
||||
}
|
||||
|
||||
QString MinecraftInstance::gameRoot() const
|
||||
{
|
||||
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
|
||||
@ -386,12 +435,6 @@ QStringList MinecraftInstance::extraArguments()
|
||||
list.append("-javaagent:" + jar[0] + (agent->argument().isEmpty() ? "" : "=" + agent->argument()));
|
||||
}
|
||||
|
||||
{
|
||||
const auto loaders = version->getModLoaders();
|
||||
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
|
||||
list.append("-Dloader.disable_beacon=true");
|
||||
}
|
||||
|
||||
{
|
||||
QString openALPath;
|
||||
QString glfwPath;
|
||||
@ -897,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
if (m_settings->get("ShowGameTime").toBool()) {
|
||||
if (lastTimePlayed() > 0) {
|
||||
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
|
||||
description.append(tr(", last played on %1 for %2")
|
||||
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
|
||||
.arg(Time::prettifyDuration(lastTimePlayed())));
|
||||
description.append(
|
||||
tr(", last played on %1 for %2")
|
||||
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
|
||||
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
|
||||
}
|
||||
|
||||
if (totalTimePlayed() > 0) {
|
||||
description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
|
||||
description.append(
|
||||
tr(", total played for %1")
|
||||
.arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
|
||||
}
|
||||
}
|
||||
if (hasCrashed()) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -70,6 +70,8 @@ class MinecraftInstance : public BaseInstance {
|
||||
|
||||
bool canExport() const override { return true; }
|
||||
|
||||
void populateLaunchMenu(QMenu* menu) override;
|
||||
|
||||
////// Directories and files //////
|
||||
QString jarModsDir() const;
|
||||
QString resourcePacksDir() const;
|
||||
|
@ -58,15 +58,14 @@
|
||||
#include "ComponentUpdateTask.h"
|
||||
#include "PackProfile.h"
|
||||
#include "PackProfile_p.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
|
||||
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.neoforged", ResourceAPI::NeoForge },
|
||||
{ "net.minecraftforge", ResourceAPI::Forge },
|
||||
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
|
||||
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
|
||||
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
|
||||
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
|
||||
{ "net.minecraftforge", ModPlatform::Forge },
|
||||
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
|
||||
{ "org.quiltmc.quilt-loader", ModPlatform::Quilt },
|
||||
{ "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
|
||||
|
||||
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
|
||||
{
|
||||
@ -990,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
|
||||
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
|
||||
{
|
||||
ResourceAPI::ModLoaderTypes result;
|
||||
ModPlatform::ModLoaderTypes result;
|
||||
bool has_any_loader = false;
|
||||
|
||||
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
|
||||
QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
|
||||
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
@ -1009,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
|
||||
{
|
||||
auto loadersOpt = getModLoaders();
|
||||
if (!loadersOpt.has_value())
|
||||
return loadersOpt;
|
||||
auto loaders = loadersOpt.value();
|
||||
// TODO: remove this or add version condition once Quilt drops official Fabric support
|
||||
if (loaders & ModPlatform::Quilt)
|
||||
loaders |= ModPlatform::Fabric;
|
||||
// TODO: remove this or add version condition once NeoForge drops official Forge support
|
||||
if (loaders & ModPlatform::NeoForge)
|
||||
loaders |= ModPlatform::Forge;
|
||||
return loaders;
|
||||
}
|
||||
|
@ -44,14 +44,11 @@
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "BaseVersion.h"
|
||||
#include "Component.h"
|
||||
#include "LaunchProfile.h"
|
||||
#include "Library.h"
|
||||
#include "MojangDownloadInfo.h"
|
||||
#include "ProfileUtils.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
class MinecraftInstance;
|
||||
@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
|
||||
// todo(merged): is this the best approach
|
||||
void appendComponent(ComponentPtr component);
|
||||
|
||||
std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
|
||||
std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
|
||||
// this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
|
||||
std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
|
||||
|
||||
private:
|
||||
void scheduleSave();
|
||||
|
@ -415,7 +415,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
|
||||
|
||||
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
||||
{
|
||||
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
|
||||
if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -423,7 +423,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
|
||||
if (value == Qt::Checked) {
|
||||
MinecraftAccountPtr account = at(idx.row());
|
||||
setDefaultAccount(account);
|
||||
}
|
||||
} else if (m_defaultAccount == at(idx.row()))
|
||||
setDefaultAccount(nullptr);
|
||||
}
|
||||
|
||||
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));
|
||||
|
@ -132,17 +132,23 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
|
||||
if (!preserve_metadata) {
|
||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||
|
||||
if (metadata()) {
|
||||
Metadata::remove(index_dir, metadata()->slug);
|
||||
} else {
|
||||
auto n = name();
|
||||
Metadata::remove(index_dir, n);
|
||||
}
|
||||
destroyMetadata(index_dir);
|
||||
}
|
||||
|
||||
return Resource::destroy(attempt_trash);
|
||||
}
|
||||
|
||||
void Mod::destroyMetadata(QDir& index_dir)
|
||||
{
|
||||
if (metadata()) {
|
||||
Metadata::remove(index_dir, metadata()->slug);
|
||||
} else {
|
||||
auto n = name();
|
||||
Metadata::remove(index_dir, n);
|
||||
}
|
||||
m_local_details.metadata = nullptr;
|
||||
}
|
||||
|
||||
auto Mod::details() const -> const ModDetails&
|
||||
{
|
||||
return m_local_details;
|
||||
@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const
|
||||
PixmapCache::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));
|
||||
auto pixmap =
|
||||
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size, mode);
|
||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
|
@ -93,6 +93,8 @@ class Mod : public Resource {
|
||||
|
||||
// Delete all the files of this mod
|
||||
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
||||
// Delete the metadata only
|
||||
void destroyMetadata(QDir& index_dir);
|
||||
|
||||
void finishResolvingWithDetails(ModDetails&& details);
|
||||
|
||||
|
@ -51,8 +51,13 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
|
||||
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
||||
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
||||
@ -228,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
auto m = at(i.row());
|
||||
auto index_dir = indexDir();
|
||||
m->destroyMetadata(index_dir);
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModFolderModel::isValid()
|
||||
{
|
||||
return m_dir.exists() && m_dir.isReadable();
|
||||
@ -309,3 +333,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||
|
||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
|
||||
static const FlameAPI flameAPI;
|
||||
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
|
||||
{
|
||||
if (vers.addonId.isValid()) {
|
||||
ModPlatform::IndexedPack pack{
|
||||
vers.addonId,
|
||||
ModPlatform::ResourceProvider::FLAME,
|
||||
};
|
||||
|
||||
QEventLoop loop;
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto job = flameAPI.getProject(vers.addonId.toString(), response);
|
||||
|
||||
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
|
||||
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
|
||||
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qDebug() << *response;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto obj = Json::requireObject(Json::requireObject(doc), "data");
|
||||
FlameMod::loadIndexedPack(pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod info: " << e.cause();
|
||||
}
|
||||
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
|
||||
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||
update_metadata.start();
|
||||
});
|
||||
|
||||
job->start();
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
return ResourceFolderModel::installResource(file_path);
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel {
|
||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||
|
||||
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
|
||||
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
|
||||
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
|
||||
|
||||
/// Deletes all the selected mods
|
||||
bool deleteMods(const QModelIndexList& indexes);
|
||||
bool deleteModsMetadata(const QModelIndexList& indexes);
|
||||
|
||||
bool isValid();
|
||||
|
||||
|
@ -50,7 +50,8 @@ void ResourcePack::setImage(QImage new_image) const
|
||||
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));
|
||||
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;
|
||||
@ -68,7 +69,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size, mode);
|
||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
|
@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const
|
||||
PixmapCache::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));
|
||||
auto pixmap =
|
||||
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
@ -56,7 +57,7 @@ QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size, mode);
|
||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
|
@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
|
||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
|
||||
}
|
||||
|
||||
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
|
||||
static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
|
||||
{
|
||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
|
||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value();
|
||||
}
|
||||
|
||||
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
||||
@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
|
||||
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
|
||||
const ModPlatform::ResourceProvider providerName)
|
||||
{
|
||||
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
|
||||
if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) {
|
||||
auto overide = ModPlatform::getOverrideDeps();
|
||||
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
|
||||
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
|
||||
@ -191,7 +191,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
}
|
||||
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
|
||||
if (!pDep->version.addonId.isValid()) {
|
||||
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
|
||||
if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
|
||||
auto overide = ModPlatform::getOverrideDeps();
|
||||
auto over = std::find_if(overide.cbegin(), overide.cend(),
|
||||
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
|
||||
@ -201,6 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
return;
|
||||
}
|
||||
}
|
||||
removePack(dep.addonId);
|
||||
qWarning() << "Error while reading mod version empty ";
|
||||
qDebug() << doc;
|
||||
return;
|
||||
|
@ -80,5 +80,5 @@ class GetModDependenciesTask : public SequentialTask {
|
||||
Provider m_modrinth_provider;
|
||||
|
||||
Version m_version;
|
||||
ResourceAPI::ModLoaderTypes m_loaderType;
|
||||
ModPlatform::ModLoaderTypes m_loaderType;
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ class CheckUpdateTask : public Task {
|
||||
public:
|
||||
CheckUpdateTask(QList<Mod*>& mods,
|
||||
std::list<Version>& mcVersions,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
|
||||
|
||||
@ -53,7 +53,7 @@ class CheckUpdateTask : public Task {
|
||||
protected:
|
||||
QList<Mod*>& m_mods;
|
||||
std::list<Version>& m_game_versions;
|
||||
std::optional<ResourceAPI::ModLoaderTypes> m_loaders;
|
||||
std::optional<ModPlatform::ModLoaderTypes> m_loaders;
|
||||
std::shared_ptr<ModFolderModel> m_mods_folder;
|
||||
|
||||
std::vector<UpdatableMod> m_updatable;
|
||||
|
@ -83,4 +83,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
|
||||
projectID.toString();
|
||||
}
|
||||
|
||||
auto getModLoaderString(ModLoaderType type) -> const QString
|
||||
{
|
||||
switch (type) {
|
||||
case NeoForge:
|
||||
return "neoforge";
|
||||
case Forge:
|
||||
return "forge";
|
||||
case Cauldron:
|
||||
return "cauldron";
|
||||
case LiteLoader:
|
||||
return "liteloader";
|
||||
case Fabric:
|
||||
return "fabric";
|
||||
case Quilt:
|
||||
return "quilt";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace ModPlatform
|
||||
|
@ -30,6 +30,9 @@ class QIODevice;
|
||||
|
||||
namespace ModPlatform {
|
||||
|
||||
enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
|
||||
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
|
||||
|
||||
enum class ResourceProvider { MODRINTH, FLAME };
|
||||
|
||||
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
|
||||
@ -70,7 +73,7 @@ struct IndexedVersion {
|
||||
QString downloadUrl;
|
||||
QString date;
|
||||
QString fileName;
|
||||
QStringList loaders = {};
|
||||
ModLoaderTypes loaders = {};
|
||||
QString hash_type;
|
||||
QString hash;
|
||||
bool is_preferred = true;
|
||||
@ -148,6 +151,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
|
||||
|
||||
QString getMetaURL(ResourceProvider provider, QVariant projectID);
|
||||
|
||||
auto getModLoaderString(ModLoaderType type) -> const QString;
|
||||
|
||||
constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept
|
||||
{
|
||||
auto x = static_cast<int>(l);
|
||||
return x && !(x & (x - 1));
|
||||
}
|
||||
|
||||
} // namespace ModPlatform
|
||||
|
||||
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
||||
|
@ -54,9 +54,6 @@ class ResourceAPI {
|
||||
public:
|
||||
virtual ~ResourceAPI() = default;
|
||||
|
||||
enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
|
||||
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
|
||||
|
||||
struct SortingMethod {
|
||||
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
|
||||
// Used by Flame in the API request.
|
||||
@ -74,7 +71,7 @@ class ResourceAPI {
|
||||
|
||||
std::optional<QString> search;
|
||||
std::optional<SortingMethod> sorting;
|
||||
std::optional<ModLoaderTypes> loaders;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders;
|
||||
std::optional<std::list<Version> > versions;
|
||||
};
|
||||
struct SearchCallbacks {
|
||||
@ -87,7 +84,7 @@ class ResourceAPI {
|
||||
ModPlatform::IndexedPack pack;
|
||||
|
||||
std::optional<std::list<Version> > mcVersions;
|
||||
std::optional<ModLoaderTypes> loaders;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders;
|
||||
|
||||
VersionSearchArgs(VersionSearchArgs const&) = default;
|
||||
void operator=(VersionSearchArgs other)
|
||||
@ -108,13 +105,15 @@ class ResourceAPI {
|
||||
void operator=(ProjectInfoArgs other) { pack = other.pack; }
|
||||
};
|
||||
struct ProjectInfoCallbacks {
|
||||
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
|
||||
std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed;
|
||||
std::function<void(QString const& reason)> on_fail;
|
||||
std::function<void()> on_abort;
|
||||
};
|
||||
|
||||
struct DependencySearchArgs {
|
||||
ModPlatform::Dependency dependency;
|
||||
Version mcVersion;
|
||||
ModLoaderTypes loader;
|
||||
ModPlatform::ModLoaderTypes loader;
|
||||
};
|
||||
|
||||
struct DependencySearchCallbacks {
|
||||
@ -161,27 +160,6 @@ class ResourceAPI {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static auto getModLoaderString(ModLoaderType type) -> const QString
|
||||
{
|
||||
switch (type) {
|
||||
case NeoForge:
|
||||
return "neoforge";
|
||||
case Forge:
|
||||
return "forge";
|
||||
case Cauldron:
|
||||
return "cauldron";
|
||||
case LiteLoader:
|
||||
return "liteloader";
|
||||
case Fabric:
|
||||
return "fabric";
|
||||
case Quilt:
|
||||
return "quilt";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
protected:
|
||||
[[nodiscard]] inline QString debugName() const { return "External resource API"; }
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "FileResolvingTask.h"
|
||||
|
||||
#include "Json.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/ApiUpload.h"
|
||||
#include "net/Upload.h"
|
||||
@ -102,7 +103,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
|
||||
auto output = std::make_shared<QByteArray>();
|
||||
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
|
||||
QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; });
|
||||
|
||||
m_checkJob->addNetAction(dl);
|
||||
blockedProjects.insert(&out, output);
|
||||
@ -153,7 +154,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
|
||||
// If there's more than one mod loader for this version, we can't know for sure
|
||||
// which file is relative to each loader, so it's best to not use any one and
|
||||
// let the user download it manually.
|
||||
if (file.loaders.size() <= 1) {
|
||||
if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
|
||||
out->url = file.downloadUrl;
|
||||
qDebug() << "Found alternative on modrinth " << out->fileName;
|
||||
} else {
|
||||
@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
||||
auto dl = Net::ApiDownload::makeByteArray(url, output);
|
||||
qDebug() << "Fetching url slug for file:" << mod->fileName;
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
|
||||
QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [block, index, output]() {
|
||||
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
|
||||
auto json = QJsonDocument::fromJson(*output);
|
||||
auto base =
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "FlameModIndex.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/ApiUpload.h"
|
||||
@ -131,19 +130,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
|
||||
auto obj = Json::requireObject(doc);
|
||||
auto arr = Json::requireArray(obj, "data");
|
||||
|
||||
QJsonObject latest_file_obj;
|
||||
ModPlatform::IndexedVersion ver_tmp;
|
||||
|
||||
for (auto file : arr) {
|
||||
auto file_obj = Json::requireObject(file);
|
||||
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
|
||||
if (file_tmp.date > ver_tmp.date) {
|
||||
ver_tmp = file_tmp;
|
||||
latest_file_obj = file_obj;
|
||||
}
|
||||
if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders))
|
||||
ver = file_tmp;
|
||||
}
|
||||
|
||||
ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
|
||||
} catch (Json::JsonException& e) {
|
||||
qCritical() << "Failed to parse response from a version request.";
|
||||
qCritical() << e.what();
|
||||
|
@ -24,7 +24,10 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
|
||||
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
|
||||
|
||||
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (NeoForge | Forge | Fabric | Quilt); }
|
||||
static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
|
||||
{
|
||||
return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt);
|
||||
}
|
||||
|
||||
private:
|
||||
static int getClassId(ModPlatform::ResourceType type)
|
||||
@ -38,21 +41,42 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
}
|
||||
}
|
||||
|
||||
static int getMappedModLoader(ModLoaderTypes loaders)
|
||||
static int getMappedModLoader(ModPlatform::ModLoaderType loaders)
|
||||
{
|
||||
// https://docs.curseforge.com/?http#tocS_ModLoaderType
|
||||
if (loaders & Forge)
|
||||
return 1;
|
||||
if (loaders & Fabric)
|
||||
return 4;
|
||||
// TODO: remove this once Quilt drops official Fabric support
|
||||
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
|
||||
return 4; // FIXME: implement multiple loaders filter
|
||||
if (loaders & NeoForge)
|
||||
return 6;
|
||||
switch (loaders) {
|
||||
case ModPlatform::Forge:
|
||||
return 1;
|
||||
case ModPlatform::Cauldron:
|
||||
return 2;
|
||||
case ModPlatform::LiteLoader:
|
||||
return 3;
|
||||
case ModPlatform::Fabric:
|
||||
return 4;
|
||||
case ModPlatform::Quilt:
|
||||
return 5;
|
||||
case ModPlatform::NeoForge:
|
||||
return 6;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
|
||||
{
|
||||
QStringList l;
|
||||
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) {
|
||||
if (types & loader) {
|
||||
l << QString::number(getMappedModLoader(loader));
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
|
||||
{
|
||||
return "[" + getModLoaderStrings(types).join(',') + "]";
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
|
||||
{
|
||||
@ -69,7 +93,7 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
|
||||
get_arguments.append("sortOrder=desc");
|
||||
if (args.loaders.has_value())
|
||||
get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
|
||||
get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
|
||||
get_arguments.append(gameVersionStr);
|
||||
|
||||
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
|
||||
@ -83,47 +107,27 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
||||
{
|
||||
auto addonId = args.pack.addonId.toString();
|
||||
QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
|
||||
QString url = QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000").arg(addonId);
|
||||
|
||||
QStringList get_parameters;
|
||||
if (args.mcVersions.has_value())
|
||||
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
|
||||
url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
|
||||
|
||||
if (args.loaders.has_value()) {
|
||||
int mappedModLoader = getMappedModLoader(args.loaders.value());
|
||||
|
||||
if (args.loaders.value() & Quilt) {
|
||||
auto overide = ModPlatform::getOverrideDeps();
|
||||
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
|
||||
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
|
||||
});
|
||||
if (over != overide.cend()) {
|
||||
mappedModLoader = 5;
|
||||
}
|
||||
}
|
||||
|
||||
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
|
||||
if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
|
||||
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
|
||||
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
||||
}
|
||||
|
||||
return url + get_parameters.join('&');
|
||||
return url;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||
{
|
||||
auto mappedModLoader = getMappedModLoader(args.loader);
|
||||
auto addonId = args.dependency.addonId.toString();
|
||||
if (args.loader & Quilt) {
|
||||
auto overide = ModPlatform::getOverrideDeps();
|
||||
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
|
||||
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
|
||||
});
|
||||
if (over != overide.cend()) {
|
||||
mappedModLoader = 5;
|
||||
}
|
||||
auto url =
|
||||
QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
|
||||
if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
|
||||
int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
|
||||
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
||||
}
|
||||
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
|
||||
.arg(addonId)
|
||||
.arg(args.mcVersion.toString())
|
||||
.arg(mappedModLoader);
|
||||
return url;
|
||||
};
|
||||
};
|
||||
|
@ -5,13 +5,11 @@
|
||||
#include <MurmurHash2.h>
|
||||
#include <memory>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "ResourceDownloadTask.h"
|
||||
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
|
@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
|
||||
public:
|
||||
FlameCheckUpdate(QList<Mod*>& mods,
|
||||
std::list<Version>& mcVersions,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
|
||||
{}
|
||||
|
@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
QVector<ModPlatform::IndexedVersion> unsortedVersions;
|
||||
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
|
||||
QString mcVersion = profile->getComponentVersion("net.minecraft");
|
||||
auto loaders = profile->getSupportedModLoaders();
|
||||
|
||||
for (auto versionIter : arr) {
|
||||
auto obj = versionIter.toObject();
|
||||
@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
if (!file.addonId.isValid())
|
||||
file.addonId = pack.addonId;
|
||||
|
||||
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||
if (file.fileId.isValid() &&
|
||||
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
|
||||
@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
|
||||
|
||||
if (str.contains('.'))
|
||||
file.mcVersion.append(str);
|
||||
auto loader = str.toLower();
|
||||
if (loader == "neoforge")
|
||||
file.loaders |= ModPlatform::NeoForge;
|
||||
if (loader == "forge")
|
||||
file.loaders |= ModPlatform::Forge;
|
||||
if (loader == "cauldron")
|
||||
file.loaders |= ModPlatform::Cauldron;
|
||||
if (loader == "liteloader")
|
||||
file.loaders |= ModPlatform::LiteLoader;
|
||||
if (loader == "fabric")
|
||||
file.loaders |= ModPlatform::Fabric;
|
||||
if (loader == "quilt")
|
||||
file.loaders |= ModPlatform::Quilt;
|
||||
}
|
||||
|
||||
file.addonId = Json::requireInteger(obj, "modId");
|
||||
@ -173,8 +188,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
|
||||
return file;
|
||||
}
|
||||
|
||||
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
|
||||
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
|
||||
{
|
||||
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
|
||||
QString mcVersion = profile->getComponentVersion("net.minecraft");
|
||||
auto loaders = profile->getSupportedModLoaders();
|
||||
QVector<ModPlatform::IndexedVersion> versions;
|
||||
for (auto versionIter : arr) {
|
||||
auto obj = versionIter.toObject();
|
||||
@ -183,7 +201,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
|
||||
if (!file.addonId.isValid())
|
||||
file.addonId = m.addonId;
|
||||
|
||||
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||
if (file.fileId.isValid() &&
|
||||
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
|
||||
versions.append(file);
|
||||
}
|
||||
|
||||
@ -192,5 +211,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
|
||||
return a.date > b.date;
|
||||
};
|
||||
std::sort(versions.begin(), versions.end(), orderSortPredicate);
|
||||
return versions.front();
|
||||
if (versions.size() != 0)
|
||||
return versions.front();
|
||||
return {};
|
||||
}
|
||||
|
@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||
const BaseInstance* inst);
|
||||
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
|
||||
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
|
||||
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
|
||||
} // namespace FlameMod
|
@ -43,12 +43,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
|
||||
FlamePackExportTask::FlamePackExportTask(const QString& name,
|
||||
const QString& version,
|
||||
const QString& author,
|
||||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter)
|
||||
: name(name)
|
||||
, version(version)
|
||||
, author(author)
|
||||
, optionalFiles(optionalFiles)
|
||||
, instance(instance)
|
||||
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
|
||||
, gameRoot(instance->gameRoot())
|
||||
@ -410,7 +412,7 @@ QByteArray FlamePackExportTask::generateIndex()
|
||||
QJsonObject file;
|
||||
file["projectID"] = mod.addonId;
|
||||
file["fileID"] = mod.version;
|
||||
file["required"] = mod.enabled;
|
||||
file["required"] = mod.enabled || !optionalFiles;
|
||||
files << file;
|
||||
}
|
||||
obj["files"] = files;
|
||||
|
@ -30,6 +30,7 @@ class FlamePackExportTask : public Task {
|
||||
FlamePackExportTask(const QString& name,
|
||||
const QString& version,
|
||||
const QString& author,
|
||||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter);
|
||||
@ -44,6 +45,7 @@ class FlamePackExportTask : public Task {
|
||||
|
||||
// inputs
|
||||
const QString name, version, author;
|
||||
const bool optionalFiles;
|
||||
const InstancePtr instance;
|
||||
MinecraftInstance* mcInstance;
|
||||
const QDir gameRoot;
|
||||
|
@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo
|
||||
|
||||
callbacks.on_succeed(doc, args.pack);
|
||||
});
|
||||
|
||||
QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); });
|
||||
QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
|
||||
return job;
|
||||
}
|
||||
|
||||
@ -131,7 +132,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
|
||||
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
|
||||
QJsonParseError parse_error{};
|
||||
|
@ -60,19 +60,19 @@ Modpack parseDirectory(QString path)
|
||||
auto name = Json::requireString(obj, "name", "name");
|
||||
auto version = Json::requireString(obj, "version", "version");
|
||||
if (name == "neoforge") {
|
||||
modpack.loaderType = ResourceAPI::NeoForge;
|
||||
modpack.loaderType = ModPlatform::NeoForge;
|
||||
modpack.version = version;
|
||||
break;
|
||||
} else if (name == "forge") {
|
||||
modpack.loaderType = ResourceAPI::Forge;
|
||||
modpack.loaderType = ModPlatform::Forge;
|
||||
modpack.version = version;
|
||||
break;
|
||||
} else if (name == "fabric") {
|
||||
modpack.loaderType = ResourceAPI::Fabric;
|
||||
modpack.loaderType = ModPlatform::Fabric;
|
||||
modpack.version = version;
|
||||
break;
|
||||
} else if (name == "quilt") {
|
||||
modpack.loaderType = ResourceAPI::Quilt;
|
||||
modpack.loaderType = ModPlatform::Quilt;
|
||||
modpack.version = version;
|
||||
break;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ struct Modpack {
|
||||
// not needed for instance creation
|
||||
QVariant jvmArgs;
|
||||
|
||||
std::optional<ResourceAPI::ModLoaderType> loaderType;
|
||||
std::optional<ModPlatform::ModLoaderType> loaderType;
|
||||
QString loaderVersion;
|
||||
|
||||
QIcon icon;
|
||||
|
@ -68,25 +68,25 @@ void PackInstallTask::copySettings()
|
||||
auto modloader = m_pack.loaderType;
|
||||
if (modloader.has_value())
|
||||
switch (modloader.value()) {
|
||||
case ResourceAPI::NeoForge: {
|
||||
case ModPlatform::NeoForge: {
|
||||
components->setComponentVersion("net.neoforged", m_pack.version, true);
|
||||
break;
|
||||
}
|
||||
case ResourceAPI::Forge: {
|
||||
case ModPlatform::Forge: {
|
||||
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
|
||||
break;
|
||||
}
|
||||
case ResourceAPI::Fabric: {
|
||||
case ModPlatform::Fabric: {
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
|
||||
break;
|
||||
}
|
||||
case ResourceAPI::Quilt: {
|
||||
case ModPlatform::Quilt: {
|
||||
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
|
||||
break;
|
||||
}
|
||||
case ResourceAPI::Cauldron:
|
||||
case ModPlatform::Cauldron:
|
||||
break;
|
||||
case ResourceAPI::LiteLoader:
|
||||
case ModPlatform::LiteLoader:
|
||||
break;
|
||||
}
|
||||
components->saveNow();
|
||||
|
@ -41,7 +41,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
|
||||
Task::Ptr ModrinthAPI::latestVersion(QString hash,
|
||||
QString hash_format,
|
||||
std::optional<std::list<Version>> mcVersions,
|
||||
std::optional<ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<QByteArray> response)
|
||||
{
|
||||
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
|
||||
@ -71,7 +71,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
|
||||
Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
|
||||
QString hash_format,
|
||||
std::optional<std::list<Version>> mcVersions,
|
||||
std::optional<ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<QByteArray> response)
|
||||
{
|
||||
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
|
||||
|
@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||
auto latestVersion(QString hash,
|
||||
QString hash_format,
|
||||
std::optional<std::list<Version>> mcVersions,
|
||||
std::optional<ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<QByteArray> response) -> Task::Ptr;
|
||||
|
||||
auto latestVersions(const QStringList& hashes,
|
||||
QString hash_format,
|
||||
std::optional<std::list<Version>> mcVersions,
|
||||
std::optional<ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<QByteArray> response) -> Task::Ptr;
|
||||
|
||||
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
|
||||
@ -35,20 +35,19 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||
|
||||
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
|
||||
|
||||
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
|
||||
static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
|
||||
{
|
||||
QStringList l;
|
||||
for (auto loader : { NeoForge, Forge, Fabric, Quilt, LiteLoader }) {
|
||||
for (auto loader :
|
||||
{ ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) {
|
||||
if (types & loader) {
|
||||
l << getModLoaderString(loader);
|
||||
}
|
||||
}
|
||||
if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
|
||||
l << getModLoaderString(Fabric);
|
||||
return l;
|
||||
}
|
||||
|
||||
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
|
||||
static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
|
||||
{
|
||||
QStringList l;
|
||||
for (auto loader : getModLoaderStrings(types)) {
|
||||
@ -141,9 +140,9 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||
return s.isEmpty() ? QString() : s;
|
||||
}
|
||||
|
||||
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool
|
||||
static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
|
||||
{
|
||||
return loaders & (NeoForge | Forge | Fabric | Quilt | LiteLoader);
|
||||
return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
|
||||
static ModrinthAPI api;
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
@ -111,11 +110,11 @@ void ModrinthCheckUpdate::executeTask()
|
||||
// so we may want to filter it
|
||||
QString loader_filter;
|
||||
if (m_loaders.has_value()) {
|
||||
static auto flags = { ResourceAPI::ModLoaderType::NeoForge, ResourceAPI::ModLoaderType::Forge,
|
||||
ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt };
|
||||
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
|
||||
ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
|
||||
for (auto flag : flags) {
|
||||
if (m_loaders.value().testFlag(flag)) {
|
||||
loader_filter = api.getModLoaderString(flag);
|
||||
loader_filter = ModPlatform::getModLoaderString(flag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
|
||||
public:
|
||||
ModrinthCheckUpdate(QList<Mod*>& mods,
|
||||
std::list<Version>& mcVersions,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
|
||||
{}
|
||||
|
@ -34,12 +34,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z
|
||||
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
|
||||
const QString& version,
|
||||
const QString& summary,
|
||||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter)
|
||||
: name(name)
|
||||
, version(version)
|
||||
, summary(summary)
|
||||
, optionalFiles(optionalFiles)
|
||||
, instance(instance)
|
||||
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
|
||||
, gameRoot(instance->gameRoot())
|
||||
@ -272,11 +274,11 @@ QByteArray ModrinthPackExportTask::generateIndex()
|
||||
QString path = iterator.key();
|
||||
const ResolvedFile& value = iterator.value();
|
||||
|
||||
QJsonObject env;
|
||||
|
||||
// detect disabled mod
|
||||
const QFileInfo pathInfo(path);
|
||||
|
||||
QJsonObject env;
|
||||
if (pathInfo.suffix() == "disabled") {
|
||||
if (optionalFiles && pathInfo.suffix() == "disabled") {
|
||||
// rename it
|
||||
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
|
||||
env["client"] = "optional";
|
||||
|
@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task {
|
||||
ModrinthPackExportTask(const QString& name,
|
||||
const QString& version,
|
||||
const QString& summary,
|
||||
bool optionalFiles,
|
||||
InstancePtr instance,
|
||||
const QString& output,
|
||||
MMCZip::FilterFunction filter);
|
||||
@ -51,6 +52,7 @@ class ModrinthPackExportTask : public Task {
|
||||
|
||||
// inputs
|
||||
const QString name, version, summary;
|
||||
const bool optionalFiles;
|
||||
const InstancePtr instance;
|
||||
MinecraftInstance* mcInstance;
|
||||
const QDir gameRoot;
|
||||
|
@ -109,19 +109,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
|
||||
pack.extraDataLoaded = true;
|
||||
}
|
||||
|
||||
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
QJsonArray& arr,
|
||||
[[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||
const BaseInstance* inst)
|
||||
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
|
||||
{
|
||||
QVector<ModPlatform::IndexedVersion> unsortedVersions;
|
||||
QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft");
|
||||
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
|
||||
QString mcVersion = profile->getComponentVersion("net.minecraft");
|
||||
auto loaders = profile->getSupportedModLoaders();
|
||||
|
||||
for (auto versionIter : arr) {
|
||||
auto obj = versionIter.toObject();
|
||||
auto file = loadIndexedPackVersion(obj);
|
||||
|
||||
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||
if (file.fileId.isValid() &&
|
||||
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
||||
@ -150,7 +150,18 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
||||
}
|
||||
auto loaders = Json::requireArray(obj, "loaders");
|
||||
for (auto loader : loaders) {
|
||||
file.loaders.append(loader.toString());
|
||||
if (loader == "neoforge")
|
||||
file.loaders |= ModPlatform::NeoForge;
|
||||
if (loader == "forge")
|
||||
file.loaders |= ModPlatform::Forge;
|
||||
if (loader == "cauldron")
|
||||
file.loaders |= ModPlatform::Cauldron;
|
||||
if (loader == "liteloader")
|
||||
file.loaders |= ModPlatform::LiteLoader;
|
||||
if (loader == "fabric")
|
||||
file.loaders |= ModPlatform::Fabric;
|
||||
if (loader == "quilt")
|
||||
file.loaders |= ModPlatform::Quilt;
|
||||
}
|
||||
file.version = Json::requireString(obj, "name");
|
||||
file.version_number = Json::requireString(obj, "version_number");
|
||||
@ -234,15 +245,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
|
||||
auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
|
||||
-> ModPlatform::IndexedVersion
|
||||
{
|
||||
QVector<ModPlatform::IndexedVersion> versions;
|
||||
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
|
||||
QString mcVersion = profile->getComponentVersion("net.minecraft");
|
||||
auto loaders = profile->getSupportedModLoaders();
|
||||
|
||||
QVector<ModPlatform::IndexedVersion> versions;
|
||||
for (auto versionIter : arr) {
|
||||
auto obj = versionIter.toObject();
|
||||
auto file = loadIndexedPackVersion(obj);
|
||||
|
||||
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
|
||||
if (file.fileId.isValid() &&
|
||||
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
|
||||
versions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
|
||||
|
@ -26,11 +26,8 @@ namespace Modrinth {
|
||||
|
||||
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
QJsonArray& arr,
|
||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||
const BaseInstance* inst);
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
|
||||
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
|
||||
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
|
||||
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
|
||||
|
||||
} // namespace Modrinth
|
||||
|
@ -218,9 +218,24 @@ void HttpMetaCache::Load()
|
||||
if (!index.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(index.readAll());
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument json = QJsonDocument::fromJson(index.readAll(), &parseError);
|
||||
|
||||
auto root = Json::requireObject(json, "HttpMetaCache root");
|
||||
// Fail if the JSON is invalid.
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCritical() << QString("Failed to parse HttpMetaCache file: %1 at offset %2")
|
||||
.arg(parseError.errorString(), QString::number(parseError.offset))
|
||||
.toUtf8();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the root is an object.
|
||||
if (!json.isObject()) {
|
||||
qCritical() << "HttpMetaCache root should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto root = json.object();
|
||||
|
||||
// check file version first
|
||||
auto version_val = Json::ensureString(root, "version");
|
||||
|
@ -44,8 +44,6 @@
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
|
||||
#include "InstancePageProvider.h"
|
||||
@ -76,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin
|
||||
|
||||
// Add custom buttons to the page container layout.
|
||||
{
|
||||
auto horizontalLayout = new QHBoxLayout();
|
||||
auto horizontalLayout = new QHBoxLayout(this);
|
||||
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
|
||||
horizontalLayout->setContentsMargins(6, -1, 6, -1);
|
||||
|
||||
auto btnHelp = new QPushButton();
|
||||
auto btnHelp = new QPushButton(this);
|
||||
btnHelp->setText(tr("Help"));
|
||||
horizontalLayout->addWidget(btnHelp);
|
||||
connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help()));
|
||||
connect(btnHelp, &QPushButton::clicked, m_container, &PageContainer::help);
|
||||
|
||||
auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
horizontalLayout->addSpacerItem(spacer);
|
||||
|
||||
m_killButton = new QPushButton();
|
||||
m_launchButton = new QToolButton(this);
|
||||
m_launchButton->setText(tr("&Launch"));
|
||||
m_launchButton->setToolTip(tr("Launch the instance"));
|
||||
m_launchButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
m_launchButton->setMinimumWidth(80); // HACK!!
|
||||
horizontalLayout->addWidget(m_launchButton);
|
||||
connect(m_launchButton, &QPushButton::clicked, this, [this] { APPLICATION->launch(m_instance); });
|
||||
|
||||
m_killButton = new QPushButton(this);
|
||||
m_killButton->setText(tr("&Kill"));
|
||||
m_killButton->setToolTip(tr("Kill the running instance"));
|
||||
m_killButton->setShortcut(QKeySequence(tr("Ctrl+K")));
|
||||
horizontalLayout->addWidget(m_killButton);
|
||||
connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked()));
|
||||
connect(m_killButton, &QPushButton::clicked, this, [this] { APPLICATION->kill(m_instance); });
|
||||
|
||||
m_launchOfflineButton = new QPushButton();
|
||||
horizontalLayout->addWidget(m_launchOfflineButton);
|
||||
m_launchOfflineButton->setText(tr("Launch Offline"));
|
||||
updateButtons();
|
||||
|
||||
m_launchDemoButton = new QPushButton();
|
||||
horizontalLayout->addWidget(m_launchDemoButton);
|
||||
m_launchDemoButton->setText(tr("Launch Demo"));
|
||||
|
||||
updateLaunchButtons();
|
||||
connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked()));
|
||||
connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked()));
|
||||
|
||||
m_closeButton = new QPushButton();
|
||||
m_closeButton = new QPushButton(this);
|
||||
m_closeButton->setText(tr("Close"));
|
||||
horizontalLayout->addWidget(m_closeButton);
|
||||
connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked()));
|
||||
connect(m_closeButton, &QPushButton::clicked, this, &QMainWindow::close);
|
||||
|
||||
m_container->addButtons(horizontalLayout);
|
||||
|
||||
connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons);
|
||||
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons);
|
||||
}
|
||||
|
||||
// restore window state
|
||||
@ -149,47 +151,18 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceWindow::updateLaunchButtons()
|
||||
void InstanceWindow::updateButtons()
|
||||
{
|
||||
if (m_instance->isRunning()) {
|
||||
m_launchOfflineButton->setEnabled(false);
|
||||
m_launchDemoButton->setEnabled(false);
|
||||
m_killButton->setText(tr("Kill"));
|
||||
m_killButton->setObjectName("killButton");
|
||||
m_killButton->setToolTip(tr("Kill the running instance"));
|
||||
} else if (!m_instance->canLaunch()) {
|
||||
m_launchOfflineButton->setEnabled(false);
|
||||
m_launchDemoButton->setEnabled(false);
|
||||
m_killButton->setText(tr("Launch"));
|
||||
m_killButton->setObjectName("launchButton");
|
||||
m_killButton->setToolTip(tr("Launch the instance"));
|
||||
m_killButton->setEnabled(false);
|
||||
} else {
|
||||
m_launchOfflineButton->setEnabled(true);
|
||||
m_launchButton->setEnabled(m_instance->canLaunch());
|
||||
m_killButton->setEnabled(m_instance->isRunning());
|
||||
|
||||
// Disable demo-mode if not available.
|
||||
auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get());
|
||||
if (instance) {
|
||||
m_launchDemoButton->setEnabled(instance->supportsDemo());
|
||||
}
|
||||
|
||||
m_killButton->setText(tr("Launch"));
|
||||
m_killButton->setObjectName("launchButton");
|
||||
m_killButton->setToolTip(tr("Launch the instance"));
|
||||
}
|
||||
// NOTE: this is a hack to force the button to recalculate its style
|
||||
m_killButton->setStyleSheet("/* */");
|
||||
m_killButton->setStyleSheet(QString());
|
||||
}
|
||||
|
||||
void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
|
||||
{
|
||||
APPLICATION->launch(m_instance, false, false, nullptr);
|
||||
}
|
||||
|
||||
void InstanceWindow::on_btnLaunchMinecraftDemo_clicked()
|
||||
{
|
||||
APPLICATION->launch(m_instance, false, true, nullptr);
|
||||
QMenu* launchMenu = m_launchButton->menu();
|
||||
if (launchMenu)
|
||||
launchMenu->clear();
|
||||
else
|
||||
launchMenu = new QMenu(this);
|
||||
m_instance->populateLaunchMenu(launchMenu);
|
||||
m_launchButton->setMenu(launchMenu);
|
||||
}
|
||||
|
||||
void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
|
||||
@ -199,18 +172,13 @@ void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> pr
|
||||
|
||||
void InstanceWindow::runningStateChanged(bool running)
|
||||
{
|
||||
updateLaunchButtons();
|
||||
updateButtons();
|
||||
m_container->refreshContainer();
|
||||
if (running) {
|
||||
selectPage("log");
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceWindow::on_closeButton_clicked()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void InstanceWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
bool proceed = true;
|
||||
@ -233,15 +201,6 @@ bool InstanceWindow::saveAll()
|
||||
return m_container->saveAll();
|
||||
}
|
||||
|
||||
void InstanceWindow::on_btnKillMinecraft_clicked()
|
||||
{
|
||||
if (m_instance->isRunning()) {
|
||||
APPLICATION->kill(m_instance);
|
||||
} else {
|
||||
APPLICATION->launch(m_instance, true, false, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
QString InstanceWindow::instanceId()
|
||||
{
|
||||
return m_instance->id();
|
||||
@ -252,17 +211,15 @@ bool InstanceWindow::selectPage(QString pageId)
|
||||
return m_container->selectPage(pageId);
|
||||
}
|
||||
|
||||
BasePage* InstanceWindow::selectedPage() const
|
||||
{
|
||||
return m_container->selectedPage();
|
||||
}
|
||||
|
||||
void InstanceWindow::refreshContainer()
|
||||
{
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
InstanceWindow::~InstanceWindow() {}
|
||||
BasePage* InstanceWindow::selectedPage() const
|
||||
{
|
||||
return m_container->selectedPage();
|
||||
}
|
||||
|
||||
bool InstanceWindow::requestClose()
|
||||
{
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "LaunchController.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
@ -53,7 +54,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
|
||||
|
||||
public:
|
||||
explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0);
|
||||
virtual ~InstanceWindow();
|
||||
virtual ~InstanceWindow() = default;
|
||||
|
||||
bool selectPage(QString pageId) override;
|
||||
BasePage* selectedPage() const override;
|
||||
@ -71,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
|
||||
void isClosing();
|
||||
|
||||
private slots:
|
||||
void on_closeButton_clicked();
|
||||
void on_btnKillMinecraft_clicked();
|
||||
void on_btnLaunchMinecraftOffline_clicked();
|
||||
void on_btnLaunchMinecraftDemo_clicked();
|
||||
|
||||
void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
|
||||
void runningStateChanged(bool running);
|
||||
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
|
||||
@ -84,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
void updateLaunchButtons();
|
||||
void updateButtons();
|
||||
|
||||
private:
|
||||
shared_qobject_ptr<LaunchTask> m_proc;
|
||||
@ -92,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
|
||||
bool m_doNotSave = false;
|
||||
PageContainer* m_container = nullptr;
|
||||
QPushButton* m_closeButton = nullptr;
|
||||
QToolButton* m_launchButton = nullptr;
|
||||
QPushButton* m_killButton = nullptr;
|
||||
QPushButton* m_launchOfflineButton = nullptr;
|
||||
QPushButton* m_launchDemoButton = nullptr;
|
||||
};
|
||||
|
@ -43,7 +43,6 @@
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "ui/dialogs/ExportToModListDialog.h"
|
||||
#include "ui_MainWindow.h"
|
||||
|
||||
#include <QDir>
|
||||
@ -90,17 +89,14 @@
|
||||
#include <news/NewsChecker.h>
|
||||
#include <tools/BaseProfiler.h>
|
||||
#include <updater/ExternalUpdater.h>
|
||||
#include "InstancePageProvider.h"
|
||||
#include "InstanceWindow.h"
|
||||
#include "JavaCommon.h"
|
||||
#include "LaunchController.h"
|
||||
|
||||
#include "ui/dialogs/AboutDialog.h"
|
||||
#include "ui/dialogs/CopyInstanceDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/EditAccountDialog.h"
|
||||
#include "ui/dialogs/ExportInstanceDialog.h"
|
||||
#include "ui/dialogs/ExportPackDialog.h"
|
||||
#include "ui/dialogs/ExportToModListDialog.h"
|
||||
#include "ui/dialogs/IconPickerDialog.h"
|
||||
#include "ui/dialogs/ImportResourceDialog.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
@ -113,17 +109,22 @@
|
||||
#include "ui/themes/ThemeManager.h"
|
||||
#include "ui/widgets/LabeledToolButton.h"
|
||||
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/VersionFile.h"
|
||||
#include "minecraft/WorldList.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourcePackFolderModel.h"
|
||||
#include "minecraft/mod/ShaderPackFolderModel.h"
|
||||
#include "minecraft/mod/TexturePackFolderModel.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
|
||||
#include "KonamiCode.h"
|
||||
|
||||
#include "InstanceCopyTask.h"
|
||||
#include "InstanceImportTask.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
@ -553,71 +554,15 @@ void MainWindow::updateMainToolBar()
|
||||
ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool());
|
||||
}
|
||||
|
||||
void MainWindow::updateToolsMenu()
|
||||
void MainWindow::updateLaunchButton()
|
||||
{
|
||||
bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
|
||||
|
||||
ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning);
|
||||
ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning);
|
||||
ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning);
|
||||
|
||||
QMenu* launchMenu = ui->actionLaunchInstance->menu();
|
||||
if (launchMenu) {
|
||||
if (launchMenu)
|
||||
launchMenu->clear();
|
||||
} else {
|
||||
else
|
||||
launchMenu = new QMenu(this);
|
||||
}
|
||||
QAction* normalLaunch = launchMenu->addAction(tr("Launch"));
|
||||
normalLaunch->setShortcut(QKeySequence::Open);
|
||||
QAction* normalLaunchOffline = launchMenu->addAction(tr("Launch Offline"));
|
||||
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
|
||||
QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo"));
|
||||
normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
|
||||
if (m_selectedInstance) {
|
||||
normalLaunch->setEnabled(m_selectedInstance->canLaunch());
|
||||
normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch());
|
||||
normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch());
|
||||
|
||||
connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true, false); });
|
||||
connect(normalLaunchOffline, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, false); });
|
||||
connect(normalLaunchDemo, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, true); });
|
||||
} else {
|
||||
normalLaunch->setDisabled(true);
|
||||
normalLaunchOffline->setDisabled(true);
|
||||
normalLaunchDemo->setDisabled(true);
|
||||
}
|
||||
|
||||
// Disable demo-mode if not available.
|
||||
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
|
||||
if (instance) {
|
||||
normalLaunchDemo->setEnabled(instance->supportsDemo());
|
||||
}
|
||||
|
||||
QString profilersTitle = tr("Profilers");
|
||||
launchMenu->addSeparator()->setText(profilersTitle);
|
||||
for (auto profiler : APPLICATION->profilers().values()) {
|
||||
QAction* profilerAction = launchMenu->addAction(profiler->name());
|
||||
QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name()));
|
||||
QString error;
|
||||
if (!profiler->check(&error)) {
|
||||
profilerAction->setDisabled(true);
|
||||
profilerOfflineAction->setDisabled(true);
|
||||
QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\".");
|
||||
profilerAction->setToolTip(profilerToolTip);
|
||||
profilerOfflineAction->setToolTip(profilerToolTip);
|
||||
} else if (m_selectedInstance) {
|
||||
profilerAction->setEnabled(m_selectedInstance->canLaunch());
|
||||
profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch());
|
||||
|
||||
connect(profilerAction, &QAction::triggered,
|
||||
[this, profiler]() { APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); });
|
||||
connect(profilerOfflineAction, &QAction::triggered,
|
||||
[this, profiler]() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); });
|
||||
} else {
|
||||
profilerAction->setDisabled(true);
|
||||
profilerOfflineAction->setDisabled(true);
|
||||
}
|
||||
}
|
||||
if (m_selectedInstance)
|
||||
m_selectedInstance->populateLaunchMenu(launchMenu);
|
||||
ui->actionLaunchInstance->setMenu(launchMenu);
|
||||
}
|
||||
|
||||
@ -927,7 +872,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
|
||||
} else {
|
||||
CustomMessageBox::selectable(this, tr("Error"),
|
||||
tr("The launcher cannot download Minecraft or update instances unless you have at least "
|
||||
"one account added.\nPlease add your Mojang or Minecraft account."),
|
||||
"one account added.\nPlease add your Microsoft or Mojang account."),
|
||||
QMessageBox::Warning)
|
||||
->show();
|
||||
}
|
||||
@ -980,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
||||
if (url.scheme().isEmpty())
|
||||
url.setScheme("file");
|
||||
|
||||
ModPlatform::IndexedVersion version;
|
||||
QMap<QString, QString> extra_info;
|
||||
QUrl local_url;
|
||||
if (!url.isLocalFile()) { // download the remote resource and identify
|
||||
@ -989,6 +935,11 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
||||
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
|
||||
QUrlQuery query(url);
|
||||
|
||||
if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) {
|
||||
qDebug() << "Invalid curseforge link:" << url;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto addonId = query.allQueryItemValues("addonId")[0];
|
||||
auto fileId = query.allQueryItemValues("fileId")[0];
|
||||
|
||||
@ -1000,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
||||
auto api = FlameAPI();
|
||||
auto job = api.getFile(addonId, fileId, array);
|
||||
|
||||
QString resource_name;
|
||||
|
||||
connect(job.get(), &Task::failed, this,
|
||||
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] {
|
||||
connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &version] {
|
||||
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
|
||||
auto doc = Json::requireDocument(*array);
|
||||
auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
|
||||
// No way to find out if it's a mod or a modpack before here
|
||||
// And also we need to check if it ends with .zip, instead of any better way
|
||||
auto fileName = Json::ensureString(data, "fileName");
|
||||
version = FlameMod::loadIndexedPackVersion(data);
|
||||
auto fileName = version.fileName;
|
||||
|
||||
// Have to use ensureString then use QUrl to get proper url encoding
|
||||
dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
|
||||
dl_url = QUrl(version.downloadUrl);
|
||||
if (!dl_url.isValid()) {
|
||||
CustomMessageBox::selectable(
|
||||
this, tr("Error"),
|
||||
@ -1024,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
||||
}
|
||||
|
||||
QFileInfo dl_file(dl_url.fileName());
|
||||
resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
|
||||
});
|
||||
|
||||
{ // drop stack
|
||||
@ -1099,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
|
||||
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
|
||||
break;
|
||||
case PackedResourceType::Mod:
|
||||
minecraftInst->loaderModList()->installMod(localFileName);
|
||||
minecraftInst->loaderModList()->installMod(localFileName, version);
|
||||
break;
|
||||
case PackedResourceType::ShaderPack:
|
||||
minecraftInst->shaderPackList()->installResource(localFileName);
|
||||
@ -1278,7 +1227,7 @@ void MainWindow::globalSettingsClosed()
|
||||
proxymodel->invalidate();
|
||||
proxymodel->sort(0);
|
||||
updateMainToolBar();
|
||||
updateToolsMenu();
|
||||
updateLaunchButton();
|
||||
updateThemeMenu();
|
||||
updateStatusCenter();
|
||||
// This needs to be done to prevent UI elements disappearing in the event the config is changed
|
||||
@ -1408,10 +1357,11 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
||||
|
||||
if (APPLICATION->instances()->trashInstance(id)) {
|
||||
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
|
||||
return;
|
||||
} else {
|
||||
APPLICATION->instances()->deleteInstance(id);
|
||||
}
|
||||
|
||||
APPLICATION->instances()->deleteInstance(id);
|
||||
APPLICATION->settings()->set("SelectedInstance", QString());
|
||||
selectionBad();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionExportInstanceZip_triggered()
|
||||
@ -1513,20 +1463,6 @@ void MainWindow::activateInstance(InstancePtr instance)
|
||||
APPLICATION->launch(instance);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionLaunchInstanceOffline_triggered()
|
||||
{
|
||||
if (m_selectedInstance) {
|
||||
APPLICATION->launch(m_selectedInstance, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionLaunchInstanceDemo_triggered()
|
||||
{
|
||||
if (m_selectedInstance) {
|
||||
APPLICATION->launch(m_selectedInstance, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_actionKillInstance_triggered()
|
||||
{
|
||||
if (m_selectedInstance && m_selectedInstance->isRunning()) {
|
||||
@ -1700,6 +1636,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
|
||||
}
|
||||
if (m_selectedInstance) {
|
||||
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
|
||||
disconnect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
|
||||
}
|
||||
QString id = current.data(InstanceList::InstanceIDRole).toString();
|
||||
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
|
||||
@ -1707,14 +1644,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
|
||||
ui->instanceToolBar->setEnabled(true);
|
||||
setInstanceActionsEnabled(true);
|
||||
ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
|
||||
ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
|
||||
ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch());
|
||||
|
||||
// Disable demo-mode if not available.
|
||||
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
|
||||
if (instance) {
|
||||
ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo());
|
||||
}
|
||||
|
||||
ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
|
||||
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
|
||||
@ -1723,18 +1652,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
|
||||
updateStatusCenter();
|
||||
updateInstanceToolIcon(m_selectedInstance->iconKey());
|
||||
|
||||
updateToolsMenu();
|
||||
updateLaunchButton();
|
||||
|
||||
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
|
||||
|
||||
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
|
||||
connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
|
||||
} else {
|
||||
ui->instanceToolBar->setEnabled(false);
|
||||
setInstanceActionsEnabled(false);
|
||||
ui->actionLaunchInstance->setEnabled(false);
|
||||
ui->actionLaunchInstanceOffline->setEnabled(false);
|
||||
ui->actionLaunchInstanceDemo->setEnabled(false);
|
||||
ui->actionKillInstance->setEnabled(false);
|
||||
APPLICATION->settings()->set("SelectedInstance", QString());
|
||||
selectionBad();
|
||||
return;
|
||||
@ -1759,11 +1683,12 @@ void MainWindow::selectionBad()
|
||||
{
|
||||
// start by reseting everything...
|
||||
m_selectedInstance = nullptr;
|
||||
m_statusLeft->setText(tr("No instance selected"));
|
||||
|
||||
statusBar()->clearMessage();
|
||||
ui->instanceToolBar->setEnabled(false);
|
||||
setInstanceActionsEnabled(false);
|
||||
updateToolsMenu();
|
||||
updateLaunchButton();
|
||||
renameButton->setText(tr("Rename Instance"));
|
||||
updateInstanceToolIcon("grass");
|
||||
|
||||
@ -1810,7 +1735,9 @@ void MainWindow::updateStatusCenter()
|
||||
|
||||
int timePlayed = APPLICATION->instances()->getTotalPlayTime();
|
||||
if (timePlayed > 0) {
|
||||
m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
|
||||
m_statusCenter->setText(
|
||||
tr("Total playtime: %1")
|
||||
.arg(Time::prettifyDuration(timePlayed, APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
|
||||
}
|
||||
}
|
||||
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
|
||||
@ -1826,7 +1753,7 @@ void MainWindow::setInstanceActionsEnabled(bool enabled)
|
||||
ui->actionCreateInstanceShortcut->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::refreshCurrentInstance([[maybe_unused]] bool running)
|
||||
void MainWindow::refreshCurrentInstance()
|
||||
{
|
||||
auto current = view->selectionModel()->currentIndex();
|
||||
instanceChanged(current, current);
|
||||
|
@ -144,10 +144,6 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void on_actionLaunchInstance_triggered();
|
||||
|
||||
void on_actionLaunchInstanceOffline_triggered();
|
||||
|
||||
void on_actionLaunchInstanceDemo_triggered();
|
||||
|
||||
void on_actionKillInstance_triggered();
|
||||
|
||||
void on_actionDeleteInstance_triggered();
|
||||
@ -155,10 +151,7 @@ class MainWindow : public QMainWindow {
|
||||
void deleteGroup();
|
||||
void undoTrashInstance();
|
||||
|
||||
inline void on_actionExportInstance_triggered()
|
||||
{
|
||||
on_actionExportInstanceZip_triggered();
|
||||
}
|
||||
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
|
||||
void on_actionExportInstanceZip_triggered();
|
||||
void on_actionExportInstanceMrPack_triggered();
|
||||
void on_actionExportInstanceFlamePack_triggered();
|
||||
@ -181,7 +174,7 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void updateMainToolBar();
|
||||
|
||||
void updateToolsMenu();
|
||||
void updateLaunchButton();
|
||||
|
||||
void updateThemeMenu();
|
||||
|
||||
@ -215,7 +208,7 @@ class MainWindow : public QMainWindow {
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
#endif
|
||||
|
||||
void refreshCurrentInstance(bool running);
|
||||
void refreshCurrentInstance();
|
||||
|
||||
private:
|
||||
void retranslateUi();
|
||||
|
@ -440,22 +440,6 @@
|
||||
<string>Ctrl+D</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLaunchInstanceOffline">
|
||||
<property name="text">
|
||||
<string>Launch &Offline</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Launch the selected instance in offline mode.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLaunchInstanceDemo">
|
||||
<property name="text">
|
||||
<string>Launch &Demo</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Launch the selected instance in demo mode.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExportInstance">
|
||||
<property name="icon">
|
||||
<iconset theme="export">
|
||||
|
@ -37,15 +37,21 @@
|
||||
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
|
||||
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
|
||||
{
|
||||
Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
|
||||
|
||||
ui->setupUi(this);
|
||||
ui->name->setText(instance->name());
|
||||
ui->name->setPlaceholderText(instance->name());
|
||||
ui->name->setText(instance->settings()->get("ExportName").toString());
|
||||
ui->version->setText(instance->settings()->get("ExportVersion").toString());
|
||||
ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
|
||||
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
|
||||
setWindowTitle("Export Modrinth Pack");
|
||||
setWindowTitle(tr("Export Modrinth Pack"));
|
||||
ui->summary->setText(instance->settings()->get("ExportSummary").toString());
|
||||
} else {
|
||||
setWindowTitle("Export CurseForge Pack");
|
||||
ui->version->setText("");
|
||||
ui->summaryLabel->setText("Author");
|
||||
setWindowTitle(tr("Export CurseForge Pack"));
|
||||
ui->summaryLabel->setText(tr("&Author"));
|
||||
ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
|
||||
}
|
||||
|
||||
// ensure a valid pack is generated
|
||||
@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
|
||||
|
||||
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
|
||||
if (mcInstance) {
|
||||
mcInstance->loaderModList()->update();
|
||||
const QDir index = mcInstance->loaderModList()->indexDir();
|
||||
if (index.exists())
|
||||
proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath()));
|
||||
proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath()));
|
||||
}
|
||||
|
||||
ui->treeView->setModel(proxy);
|
||||
ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
|
||||
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
|
||||
ui->files->setModel(proxy);
|
||||
ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
|
||||
ui->files->sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
model->setFilter(filter);
|
||||
model->setRootPath(instance->gameRoot());
|
||||
|
||||
QHeaderView* headerView = ui->treeView->header();
|
||||
QHeaderView* headerView = ui->files->header();
|
||||
headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
}
|
||||
@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog()
|
||||
|
||||
void ExportPackDialog::done(int result)
|
||||
{
|
||||
if (result == Accepted) {
|
||||
const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
|
||||
QString output;
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
|
||||
FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
|
||||
nullptr);
|
||||
else
|
||||
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
|
||||
FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
|
||||
auto settings = instance->settings();
|
||||
settings->set("ExportName", ui->name->text());
|
||||
settings->set("ExportVersion", ui->version->text());
|
||||
settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
|
||||
settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
|
||||
|
||||
if (result == Accepted) {
|
||||
const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
|
||||
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
||||
|
||||
QString output;
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
|
||||
"Modrinth pack (*.mrpack *.zip)", nullptr);
|
||||
if (output.isEmpty())
|
||||
return;
|
||||
if (!(output.endsWith(".zip") || output.endsWith(".mrpack")))
|
||||
output.append(".mrpack");
|
||||
} else {
|
||||
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"),
|
||||
"CurseForge pack (*.zip)", nullptr);
|
||||
if (output.isEmpty())
|
||||
return;
|
||||
if (!output.endsWith(".zip"))
|
||||
output.append(".zip");
|
||||
}
|
||||
|
||||
if (output.isEmpty())
|
||||
return;
|
||||
Task* task;
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||
task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
else
|
||||
task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
|
||||
output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
} else {
|
||||
task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
}
|
||||
|
||||
connect(task, &Task::failed,
|
||||
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
@ -140,7 +160,6 @@ void ExportPackDialog::done(int result)
|
||||
|
||||
void ExportPackDialog::validate()
|
||||
{
|
||||
const bool invalid =
|
||||
ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty());
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty());
|
||||
}
|
||||
|
@ -7,12 +7,9 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>650</width>
|
||||
<height>413</height>
|
||||
<height>510</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Export Pack</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@ -20,13 +17,16 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="information">
|
||||
<property name="title">
|
||||
<string>Information</string>
|
||||
<string>&Description</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="summaryLabel">
|
||||
<property name="text">
|
||||
<string>Summary</string>
|
||||
<string>&Summary</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>summary</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -36,14 +36,20 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
<string>&Name</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>name</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
<string>&Version</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>version</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -57,31 +63,52 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="filesLabel">
|
||||
<property name="text">
|
||||
<string>Files</string>
|
||||
<widget class="QGroupBox" name="options">
|
||||
<property name="title">
|
||||
<string>&Options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="filesLabel">
|
||||
<property name="text">
|
||||
<string>&Files</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>files</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="files">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="optionalFiles">
|
||||
<property name="text">
|
||||
<string>&Mark disabled files as optional</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -97,7 +124,8 @@
|
||||
<tabstop>name</tabstop>
|
||||
<tabstop>version</tabstop>
|
||||
<tabstop>summary</tabstop>
|
||||
<tabstop>treeView</tabstop>
|
||||
<tabstop>files</tabstop>
|
||||
<tabstop>optionalFiles</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
@ -5,8 +5,6 @@
|
||||
#include "ScrollMessageBox.h"
|
||||
#include "ui_ReviewMessageBox.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "Markdown.h"
|
||||
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
@ -30,9 +28,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
|
||||
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
|
||||
}
|
||||
|
||||
static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst)
|
||||
static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst)
|
||||
{
|
||||
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() };
|
||||
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() };
|
||||
}
|
||||
|
||||
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
|
||||
|
@ -279,7 +279,7 @@ QList<BasePage*> ModDownloadDialog::getPages()
|
||||
{
|
||||
QList<BasePage*> pages;
|
||||
|
||||
auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value();
|
||||
auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getSupportedModLoaders().value();
|
||||
|
||||
if (ModrinthAPI::validateModLoaders(loaders))
|
||||
pages.append(ModrinthModPage::create(this, *m_instance));
|
||||
|
@ -64,7 +64,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
|
||||
ui->setupUi(this);
|
||||
ui->listView->setEmptyString(
|
||||
tr("Welcome!\n"
|
||||
"If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account."));
|
||||
"If you're new here, you can select the \"Add Microsoft\" or \"Add Mojang\" buttons to link your Microsoft and/or Mojang "
|
||||
"accounts."));
|
||||
ui->listView->setEmptyMode(VersionListView::String);
|
||||
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
@ -85,6 +86,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged,
|
||||
[this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); });
|
||||
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
|
||||
connect(ui->listView, &VersionListView::activated, this,
|
||||
[this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); });
|
||||
|
||||
connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged);
|
||||
connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged);
|
||||
|
@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
|
||||
{
|
||||
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
|
||||
unsigned int maxMem = ui->maxMemSpinBox->value();
|
||||
unsigned int minMem = ui->minMemSpinBox->value();
|
||||
|
||||
QString iconName;
|
||||
|
||||
@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
|
||||
} else if (maxMem > (sysMiB * 0.9)) {
|
||||
iconName = "status-yellow";
|
||||
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
|
||||
} else if (maxMem < minMem) {
|
||||
iconName = "status-yellow";
|
||||
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
|
||||
} else {
|
||||
iconName = "status-good";
|
||||
ui->labelMaxMemIcon->setToolTip("");
|
||||
|
@ -2,7 +2,6 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2023 seth <getchoo at tuta dot io>
|
||||
*
|
||||
* 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
|
||||
@ -115,13 +114,11 @@ void MinecraftPage::applySettings()
|
||||
s->set("ShowGameTime", ui->showGameTime->isChecked());
|
||||
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
|
||||
s->set("RecordGameTime", ui->recordGameTime->isChecked());
|
||||
s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());
|
||||
|
||||
// Miscellaneous
|
||||
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
|
||||
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
|
||||
|
||||
// Mod loader settings
|
||||
s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
|
||||
}
|
||||
|
||||
void MinecraftPage::loadSettings()
|
||||
@ -169,11 +166,10 @@ void MinecraftPage::loadSettings()
|
||||
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
|
||||
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
|
||||
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
|
||||
ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());
|
||||
|
||||
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
|
||||
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
|
||||
|
||||
ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());
|
||||
}
|
||||
|
||||
void MinecraftPage::retranslate()
|
||||
|
@ -138,6 +138,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGameTimeWithoutDays">
|
||||
<property name="text">
|
||||
<string>Show time spent playing in hours</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -190,25 +197,6 @@
|
||||
<string>Tweaks</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="modLoaderSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Mod loader settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
|
||||
<property name="text">
|
||||
<string>Disable Quilt Loader Beacon</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Disable Quilt loader's beacon for counting monthly active users</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
|
||||
<property name="title">
|
||||
|
@ -152,6 +152,7 @@ void ExternalResourcesPage::retranslate()
|
||||
void ExternalResourcesPage::itemActivated(const QModelIndex&)
|
||||
{
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
||||
m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
|
||||
}
|
||||
|
||||
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
|
||||
|
@ -168,6 +168,17 @@
|
||||
<string>Go to mods home page</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemoveItemMetadata">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove metadata</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove mod's metadata</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -3,7 +3,6 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 seth <getchoo at tuta dot io>
|
||||
*
|
||||
* 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
|
||||
@ -254,14 +253,6 @@ void InstanceSettingsPage::applySettings()
|
||||
m_settings->reset("InstanceAccountId");
|
||||
}
|
||||
|
||||
bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked();
|
||||
m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings);
|
||||
if (overrideModLoaderSettings) {
|
||||
m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
|
||||
} else {
|
||||
m_settings->reset("DisableQuiltBeacon");
|
||||
}
|
||||
|
||||
// FIXME: This should probably be called by a signal instead
|
||||
m_instance->updateRuntimeContext();
|
||||
}
|
||||
@ -365,10 +356,6 @@ void InstanceSettingsPage::loadSettings()
|
||||
|
||||
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
|
||||
updateAccountsMenu();
|
||||
|
||||
// Mod loader specific settings
|
||||
ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool());
|
||||
ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool());
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaDetectBtn_clicked()
|
||||
@ -491,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
|
||||
{
|
||||
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
|
||||
unsigned int maxMem = ui->maxMemSpinBox->value();
|
||||
unsigned int minMem = ui->minMemSpinBox->value();
|
||||
|
||||
QString iconName;
|
||||
|
||||
@ -500,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
|
||||
} else if (maxMem > (sysMiB * 0.9)) {
|
||||
iconName = "status-yellow";
|
||||
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
|
||||
} else if (maxMem < minMem) {
|
||||
iconName = "status-yellow";
|
||||
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
|
||||
} else {
|
||||
iconName = "status-good";
|
||||
ui->labelMaxMemIcon->setToolTip("");
|
||||
|
@ -583,31 +583,6 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="modLoaderSettingsGroupBox">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Mod loader settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="VerticalLayout_16">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
|
||||
<property name="text">
|
||||
<string>Disable Quilt Loader Beacon</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Disable Quilt loader's beacon for counting monthly active users</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||
<property name="enabled">
|
||||
|
@ -92,6 +92,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
|
||||
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
|
||||
|
||||
ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
|
||||
ui->actionsToolbar->insertActionAfter(ui->actionRemoveItem, ui->actionRemoveItemMetadata);
|
||||
connect(ui->actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
|
||||
|
||||
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
|
||||
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
|
||||
@ -104,11 +108,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
if (selected <= 1) {
|
||||
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
|
||||
|
||||
ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
|
||||
} else {
|
||||
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
|
||||
|
||||
ui->actionRemoveItemMetadata->setToolTip(tr("Remove mods' metadata"));
|
||||
}
|
||||
ui->actionVisitItemPage->setEnabled(selected != 0);
|
||||
ui->actionRemoveItemMetadata->setEnabled(selected != 0);
|
||||
});
|
||||
|
||||
connect(mods.get(), &ModFolderModel::rowsInserted, this,
|
||||
@ -297,3 +306,24 @@ void ModFolderPage::visitModPages()
|
||||
DesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderPage::deleteModMetadata()
|
||||
{
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||
auto selectionCount = m_model->selectedMods(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 mods.\n"
|
||||
"Are you sure?")
|
||||
.arg(selectionCount),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
|
||||
m_model->deleteModsMetadata(selection);
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ class ModFolderPage : public ExternalResourcesPage {
|
||||
|
||||
private slots:
|
||||
void removeItems(const QItemSelection& selection) override;
|
||||
void deleteModMetadata();
|
||||
|
||||
void installMods();
|
||||
void updateMods();
|
||||
|
@ -3,7 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {
|
||||
}
|
||||
}
|
||||
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override
|
||||
{
|
||||
return parent.isValid() ? 0 : m_servers.size();
|
||||
}
|
||||
int columnCount(const QModelIndex& parent) const override
|
||||
{
|
||||
return parent.isValid() ? 0 : COLUMN_COUNT;
|
||||
}
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_servers.size(); }
|
||||
int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : COLUMN_COUNT; }
|
||||
|
||||
Server* at(int index)
|
||||
{
|
||||
@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
|
||||
qDebug() << "Changed:" << path;
|
||||
load();
|
||||
}
|
||||
void fileChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Changed:" << path;
|
||||
}
|
||||
void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
|
||||
|
||||
private slots:
|
||||
void save_internal()
|
||||
@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
|
||||
m_saveTimer.stop();
|
||||
}
|
||||
|
||||
bool saveIsScheduled() const
|
||||
{
|
||||
return m_dirty;
|
||||
}
|
||||
bool saveIsScheduled() const { return m_dirty; }
|
||||
|
||||
void updateFSObserver()
|
||||
{
|
||||
@ -743,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()
|
||||
void ServersPage::on_actionJoin_triggered()
|
||||
{
|
||||
const auto& address = m_model->at(currentServer)->m_address;
|
||||
APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
|
||||
APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
|
||||
}
|
||||
|
||||
#include "ServersPage.moc"
|
||||
|
@ -166,14 +166,17 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow
|
||||
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
|
||||
auto smodel = ui->packageView->selectionModel();
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
|
||||
|
||||
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
|
||||
updateVersionControls();
|
||||
preselect(0);
|
||||
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
|
||||
connect(ui->packageView, &QAbstractItemView::activated, this, [this](const QModelIndex& index) {
|
||||
auto component = m_profile->getComponent(index.row());
|
||||
component->setEnabled(!component->isEnabled());
|
||||
});
|
||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
|
||||
}
|
||||
|
||||
@ -408,7 +411,7 @@ void VersionPage::on_actionDownload_All_triggered()
|
||||
if (!APPLICATION->accounts()->anyAccountIsValid()) {
|
||||
CustomMessageBox::selectable(this, tr("Error"),
|
||||
tr("Cannot download Minecraft or update instances unless you have at least "
|
||||
"one account added.\nPlease add your Mojang or Minecraft account."),
|
||||
"one account added.\nPlease add your Microsoft or Mojang account."),
|
||||
QMessageBox::Warning)
|
||||
->show();
|
||||
return;
|
||||
|
@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
|
||||
|
||||
auto sort = getCurrentSortingMethodByIndex();
|
||||
|
||||
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
|
||||
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
|
||||
}
|
||||
|
||||
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
|
||||
@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
|
||||
if (!m_filter->versions.empty())
|
||||
versions = m_filter->versions;
|
||||
|
||||
return { pack, versions, profile->getModLoaders() };
|
||||
return { pack, versions, profile->getSupportedModLoaders() };
|
||||
}
|
||||
|
||||
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
|
||||
|
@ -124,8 +124,7 @@ void ModPage::updateVersionList()
|
||||
auto version = current_pack->versions[i];
|
||||
bool valid = false;
|
||||
for (auto& mcVer : m_filter->versions) {
|
||||
// NOTE: Flame doesn't care about loader, so passing it changes nothing.
|
||||
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
|
||||
if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
|
||||
|
||||
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;
|
||||
|
||||
[[nodiscard]] bool supportsFiltering() const override { return true; };
|
||||
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
||||
|
@ -132,6 +132,32 @@ void ResourceModel::search()
|
||||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
if (m_search_term.startsWith("#")) {
|
||||
auto projectId = m_search_term.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::ProjectInfoCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestFailed(reason, -1);
|
||||
};
|
||||
callbacks.on_abort = [this] {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestAborted();
|
||||
};
|
||||
|
||||
callbacks.on_succeed = [this](auto& doc, auto& pack) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestForOneSucceeded(doc);
|
||||
};
|
||||
if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job)
|
||||
runSearchJob(job);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto args{ createSearchArguments() };
|
||||
|
||||
auto callbacks{ createSearchCallbacks() };
|
||||
@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
|
||||
|
||||
// Use default if no callbacks are set
|
||||
if (!callbacks.on_succeed)
|
||||
callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
|
||||
callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
auto pack = newpack;
|
||||
infoRequestSucceeded(doc, pack, entry);
|
||||
};
|
||||
if (!callbacks.on_fail)
|
||||
callbacks.on_fail = [this](QString reason) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
|
||||
};
|
||||
|
||||
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
|
||||
runInfoJob(job);
|
||||
@ -372,6 +405,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
||||
{
|
||||
ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
|
||||
try {
|
||||
auto obj = Json::requireObject(doc);
|
||||
if (obj.contains("data"))
|
||||
obj = Json::requireObject(obj, "data");
|
||||
loadIndexedPack(*pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
|
||||
}
|
||||
|
||||
m_search_state = SearchState::Finished;
|
||||
|
||||
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
|
||||
m_packs.append(pack);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
|
||||
{
|
||||
switch (network_error_code) {
|
||||
|
@ -149,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
|
||||
private:
|
||||
/* Default search request callbacks */
|
||||
void searchRequestSucceeded(QJsonDocument&);
|
||||
void searchRequestForOneSucceeded(QJsonDocument&);
|
||||
void searchRequestFailed(QString reason, int network_error_code);
|
||||
void searchRequestAborted();
|
||||
|
||||
|
@ -44,9 +44,6 @@
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "Markdown.h"
|
||||
#include "ResourceDownloadTask.h"
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
|
||||
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||
#include "ui/pages/modplatform/ResourceModel.h"
|
||||
|
@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "ShaderPackPage.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "ui_ResourcePage.h"
|
||||
|
||||
#include "ShaderPackModel.h"
|
||||
@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
|
||||
const std::shared_ptr<ResourceFolderModel> base_model)
|
||||
{
|
||||
QString custom_target_folder;
|
||||
if (version.loaders.contains(QStringLiteral("canvas")))
|
||||
if (version.loaders & ModPlatform::Cauldron)
|
||||
custom_target_folder = QStringLiteral("resourcepacks");
|
||||
m_model->addPack(pack, version, base_model, false, custom_target_folder);
|
||||
}
|
||||
|
@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
|
||||
if (searchTerm.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
|
||||
if (searchTerm.startsWith("#"))
|
||||
return QString::number(pack.id) == searchTerm.mid(1);
|
||||
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <Json.h>
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
namespace Atl {
|
||||
|
||||
@ -46,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
ATLauncher::IndexedPack pack = modpacks.at(pos);
|
||||
if (role == Qt::DisplayRole) {
|
||||
return pack.name;
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
return pack.name;
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
if (m_logoMap.contains(pack.safeName)) {
|
||||
return (m_logoMap.value(pack.safeName));
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole: {
|
||||
if (pack.description.length() > 100) {
|
||||
// some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
}
|
||||
return pack.description;
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
case Qt::DecorationRole: {
|
||||
if (m_logoMap.contains(pack.safeName)) {
|
||||
return (m_logoMap.value(pack.safeName));
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
|
||||
return icon;
|
||||
} else if (role == Qt::UserRole) {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
return icon;
|
||||
}
|
||||
case Qt::UserRole: {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return pack.name;
|
||||
case Qt::SizeHintRole:
|
||||
return QSize(0, 58);
|
||||
// Custom data
|
||||
case UserDataTypes::TITLE:
|
||||
return pack.name;
|
||||
case UserDataTypes::DESCRIPTION:
|
||||
return pack.description;
|
||||
case UserDataTypes::SELECTED:
|
||||
return false;
|
||||
case UserDataTypes::INSTALLED:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
return {};
|
||||
}
|
||||
|
||||
void ListModel::request()
|
||||
|
@ -35,11 +35,11 @@
|
||||
*/
|
||||
|
||||
#include "AtlPage.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "ui_AtlPage.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "AtlOptionalModDialog.h"
|
||||
#include "AtlUserInteractionSupportImpl.h"
|
||||
#include "modplatform/atlauncher/ATLPackInstallTask.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
|
||||
|
||||
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||
}
|
||||
|
||||
AtlPage::~AtlPage()
|
||||
|
@ -11,44 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
@ -68,7 +31,34 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
@ -78,6 +68,31 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "FlameModel.h"
|
||||
#include <Json.h>
|
||||
#include "Application.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
@ -161,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
|
||||
|
||||
void ListModel::performPaginatedSearch()
|
||||
{
|
||||
if (currentSearchTerm.startsWith("#")) {
|
||||
auto projectId = currentSearchTerm.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::ProjectInfoCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
|
||||
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
|
||||
static const FlameAPI api;
|
||||
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
|
||||
jobPtr = job;
|
||||
jobPtr->start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
|
||||
auto searchUrl = QString(
|
||||
"https://api.curseforge.com/v1/mods/search?"
|
||||
@ -189,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
|
||||
}
|
||||
currentSearchTerm = term;
|
||||
currentSort = sort;
|
||||
if (jobPtr) {
|
||||
if (hasActiveSearchJob()) {
|
||||
jobPtr->abort();
|
||||
searchState = ResetRequested;
|
||||
return;
|
||||
} else {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
}
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
|
||||
nextSearchOffset = 0;
|
||||
performPaginatedSearch();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
@ -246,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
auto packObj = Json::ensureObject(doc.object(), "data");
|
||||
|
||||
Flame::IndexedPack pack;
|
||||
try {
|
||||
Flame::loadIndexedPack(pack, packObj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qWarning() << "Error while loading pack from CurseForge: " << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
|
||||
modpacks.append({ pack });
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
|
||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||
void searchWithTerm(const QString& term, const int sort);
|
||||
|
||||
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
||||
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
||||
|
||||
private slots:
|
||||
void performPaginatedSearch();
|
||||
|
||||
@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
|
||||
|
||||
void searchRequestFinished();
|
||||
void searchRequestFailed(QString reason);
|
||||
void searchRequestForOneSucceeded(QJsonDocument&);
|
||||
|
||||
private:
|
||||
void requestLogo(QString file, QString url);
|
||||
@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
|
||||
int currentSort = 0;
|
||||
int nextSearchOffset = 0;
|
||||
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
||||
NetJob::Ptr jobPtr;
|
||||
Task::Ptr jobPtr;
|
||||
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||
};
|
||||
|
||||
|
@ -50,7 +50,8 @@
|
||||
|
||||
static FlameAPI api;
|
||||
|
||||
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
|
||||
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
|
||||
@ -61,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
|
||||
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
|
||||
|
||||
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
|
||||
m_search_timer.setSingleShot(true);
|
||||
|
||||
connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
|
||||
|
||||
m_fetch_progress.hideIfInactive(true);
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
|
||||
// index is used to set the sorting with the curseforge api
|
||||
ui->sortByBox->addItem(tr("Sort by Featured"));
|
||||
ui->sortByBox->addItem(tr("Sort by Popularity"));
|
||||
@ -90,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
|
||||
triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
} else {
|
||||
if (m_search_timer.isActive())
|
||||
m_search_timer.stop();
|
||||
|
||||
m_search_timer.start(350);
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
@ -114,6 +131,7 @@ void FlamePage::openedImpl()
|
||||
void FlamePage::triggerSearch()
|
||||
{
|
||||
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
|
||||
m_fetch_progress.watch(listModel->activeSearchJob().get());
|
||||
}
|
||||
|
||||
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
||||
|
@ -39,8 +39,9 @@
|
||||
|
||||
#include <Application.h>
|
||||
#include <modplatform/flame/FlamePackIndex.h>
|
||||
#include "tasks/Task.h"
|
||||
#include <QTimer>
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "ui/widgets/ProgressWidget.h"
|
||||
|
||||
namespace Ui {
|
||||
class FlamePage;
|
||||
@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
|
||||
Flame::IndexedPack current;
|
||||
|
||||
int m_selected_version_index = -1;
|
||||
|
||||
ProgressWidget m_fetch_progress;
|
||||
|
||||
// Used to do instant searching with a delay to cache quick changes
|
||||
QTimer m_search_timer;
|
||||
};
|
||||
|
@ -47,7 +47,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
@ -77,7 +77,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
|
@ -31,7 +31,7 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
|
||||
|
||||
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
|
||||
{
|
||||
return FlameMod::loadDependencyVersions(m, arr);
|
||||
return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
|
@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
|
||||
|
||||
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
|
||||
{
|
||||
Q_UNUSED(loaders);
|
||||
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
|
||||
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
|
||||
(!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
|
||||
}
|
||||
|
||||
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
|
@ -95,7 +95,7 @@ class FlameModPage : public ModPage {
|
||||
|
||||
bool validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user