diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp index 241d9a49..e2ab4788 100644 --- a/api/logic/minecraft/ComponentUpdateTask.cpp +++ b/api/logic/minecraft/ComponentUpdateTask.cpp @@ -200,7 +200,7 @@ void ComponentUpdateTask::loadComponents() } } // load all the components OR their lists... - for (auto component: d->m_list->d->components) + for (auto component: d->m_list->d->persitentData.components) { shared_qobject_ptr loadTask; LoadResult singleResult; @@ -511,7 +511,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) * * NOTE: this is a placeholder and should eventually be replaced with something 'serious' */ - auto & components = d->m_list->d->components; + auto & components = d->m_list->d->persitentData.components; auto & componentIndex = d->m_list->d->componentIndex; RequireExSet allRequires; diff --git a/api/logic/minecraft/ModpackInfo.h b/api/logic/minecraft/ModpackInfo.h new file mode 100644 index 00000000..36718824 --- /dev/null +++ b/api/logic/minecraft/ModpackInfo.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +struct MaybeInt { + MaybeInt() = default; + MaybeInt(const int value): + hasValue(true), + content(value) + {}; + + operator bool() const { + return hasValue; + } + void operator=(int value) { + set(value); + } + int operator *() const { + return get(); + } + + void set(int value) { + if(value == -1) { + hasValue = false; + content = 0; + return; + } + hasValue = true; + content = value; + } + int get() const { + if(hasValue) { + return content; + } + return 0; + } + void reset() { + hasValue = false; + content = 0; + } +private: + bool hasValue = false; + int content = 0; +}; + +struct PackProfileModpackInfo +{ + operator bool() const { + return hasValue; + } + bool hasValue = false; + + QString platform; + QString repository; + QString coordinate; + + MaybeInt minHeap; + MaybeInt optimalHeap; + MaybeInt maxHeap; + QString recommendedArgs; +}; diff --git a/api/logic/minecraft/PackProfile.cpp b/api/logic/minecraft/PackProfile.cpp index 15f2f55d..853f9b2f 100644 --- a/api/logic/minecraft/PackProfile.cpp +++ b/api/logic/minecraft/PackProfile.cpp @@ -120,16 +120,42 @@ static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & co } // Save the given component container data to a file -static bool savePackProfile(const QString & filename, const ComponentContainer & container) +static bool savePackProfile(const QString & filename, const PersistentPackProfileData & container) { QJsonObject obj; obj.insert("formatVersion", currentComponentsFileVersion); QJsonArray orderArray; - for(auto component: container) + for(auto component: container.components) { orderArray.append(componentToJsonV1(component)); } obj.insert("components", orderArray); + + if(container.modpackInfo) + { + const auto & info = container.modpackInfo; + QJsonObject out; + out["platform"] = info.platform; + if(!info.repository.isNull()) { + out["repository"] = info.repository; + } + if(!info.coordinate.isNull()) { + out["coordinate"] = info.coordinate; + } + if(!info.recommendedArgs.isNull()) { + out["recommendedArgs"] = info.recommendedArgs; + } + if(info.minHeap) { + out["minHeap"] = *info.minHeap; + } + if(info.maxHeap) { + out["maxHeap"] = *info.maxHeap; + } + if(info.optimalHeap) { + out["optimalHeap"] = *info.optimalHeap; + } + obj.insert("modpackInfo", out); + } QSaveFile outFile(filename); if (!outFile.open(QFile::WriteOnly)) { @@ -153,7 +179,7 @@ static bool savePackProfile(const QString & filename, const ComponentContainer & } // Read the given file into component containers -static bool loadPackProfile(PackProfile * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container) +static bool loadPackProfile(PackProfile * parent, const QString & filename, const QString & componentJsonPattern, PersistentPackProfileData & container) { QFile componentsFile(filename); if (!componentsFile.exists()) @@ -194,13 +220,34 @@ static bool loadPackProfile(PackProfile * parent, const QString & filename, cons for(auto item: orderArray) { auto obj = Json::requireObject(item, "Component must be an object."); - container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); + container.components.append(componentFromJsonV1(parent, componentJsonPattern, obj)); + } + try + { + PackProfileModpackInfo result; + auto modpackInfoObj = Json::ensureObject(obj, "modpackInfo", {}); + if(!modpackInfoObj.isEmpty()) { + result.hasValue = true; + result.platform = Json::requireString(modpackInfoObj, "platform"); + result.repository = Json::ensureString(modpackInfoObj, "repository", QString()); + result.coordinate = Json::ensureString(modpackInfoObj, "coordinate", QString()); + result.maxHeap = Json::ensureInteger(modpackInfoObj, "maxHeap", -1); + result.minHeap = Json::ensureInteger(modpackInfoObj, "minHeap", -1); + result.optimalHeap = Json::ensureInteger(modpackInfoObj, "optimalHeap", -1); + result.recommendedArgs = Json::ensureString(modpackInfoObj, "recommendedArgs", QString()); + } + } + catch (const JSONValidationError &err) + { + qCritical() << "Couldn't parse modpack info in " << componentsFile.fileName() << ": bad file format"; + container.modpackInfo = {}; } } catch (const JSONValidationError &err) { qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; - container.clear(); + container.components.clear(); + return false; } return true; @@ -264,7 +311,7 @@ void PackProfile::save_internal() { qDebug() << "Component list save performed now for" << d->m_instance->name(); auto filename = componentsFilePath(); - savePackProfile(filename, d->components); + savePackProfile(filename, d->persitentData); d->dirty = false; } @@ -285,38 +332,37 @@ bool PackProfile::load() } // load the new component list and swap it with the current one... - ComponentContainer newComponents; - if(!loadPackProfile(this, filename, patchesPattern(), newComponents)) + PersistentPackProfileData newData; + if(!loadPackProfile(this, filename, patchesPattern(), newData)) { qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); return false; } - else + + // FIXME: actually use fine-grained updates, not this... + beginResetModel(); + // disconnect all the old components + for(auto component: d->persitentData.components) { - // FIXME: actually use fine-grained updates, not this... - beginResetModel(); - // disconnect all the old components - for(auto component: d->components) - { - disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - } - d->components.clear(); - d->componentIndex.clear(); - for(auto component: newComponents) - { - if(d->componentIndex.contains(component->m_uid)) - { - qWarning() << "Ignoring duplicate component entry" << component->m_uid; - continue; - } - connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; + disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); } + d->persitentData.components.clear(); + d->componentIndex.clear(); + for(auto component: newData.components) + { + if(d->componentIndex.contains(component->m_uid)) + { + qWarning() << "Ignoring duplicate component entry" << component->m_uid; + continue; + } + connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); + d->persitentData.components.append(component); + d->componentIndex[component->m_uid] = component; + } + d->persitentData.modpackInfo = newData.modpackInfo; + endResetModel(); + d->loaded = true; + return true; } void PackProfile::reload(Net::Mode netmode) @@ -436,7 +482,8 @@ bool PackProfile::migratePreComponentConfig() // upgrade the very old files from the beginnings of MultiMC 5 upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); - QList components; + PersistentPackProfileData data; + auto & components = data.components; QSet loaded; auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) @@ -598,14 +645,14 @@ bool PackProfile::migratePreComponentConfig() } } // new we have a complete list of components... - return savePackProfile(componentsFilePath(), components); + return savePackProfile(componentsFilePath(), data); } // END: save/load void PackProfile::appendComponent(ComponentPtr component) { - insertComponent(d->components.size(), component); + insertComponent(d->persitentData.components.size(), component); } void PackProfile::insertComponent(size_t index, ComponentPtr component) @@ -622,7 +669,7 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component) return; } beginInsertRows(QModelIndex(), index, index); - d->components.insert(index, component); + d->persitentData.components.insert(index, component); d->componentIndex[id] = component; endInsertRows(); connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); @@ -642,7 +689,7 @@ void PackProfile::componentDataChanged() } // figure out which one is it... in a seriously dumb way. int index = 0; - for (auto component: d->components) + for (auto component: d->persitentData.components) { if(component.get() == objPtr) { @@ -671,7 +718,7 @@ bool PackProfile::remove(const int index) } beginRemoveRows(QModelIndex(), index, index); - d->components.removeAt(index); + d->persitentData.components.removeAt(index); d->componentIndex.remove(patch->getID()); endRemoveRows(); invalidateLaunchProfile(); @@ -682,7 +729,7 @@ bool PackProfile::remove(const int index) bool PackProfile::remove(const QString id) { int i = 0; - for (auto patch : d->components) + for (auto patch : d->persitentData.components) { if (patch->getID() == id) { @@ -741,11 +788,11 @@ Component * PackProfile::getComponent(const QString &id) Component * PackProfile::getComponent(int index) { - if(index < 0 || index >= d->components.size()) + if(index < 0 || index >= d->persitentData.components.size()) { return nullptr; } - return d->components[index].get(); + return d->persitentData.components[index].get(); } QVariant PackProfile::data(const QModelIndex &index, int role) const @@ -756,10 +803,10 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const int row = index.row(); int column = index.column(); - if (row < 0 || row >= d->components.size()) + if (row < 0 || row >= d->persitentData.components.size()) return QVariant(); - auto patch = d->components.at(row); + auto patch = d->persitentData.components.at(row); switch (role) { @@ -831,7 +878,7 @@ bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int r if (role == Qt::CheckStateRole) { - auto component = d->components[index.row()]; + auto component = d->persitentData.components[index.row()]; if (component->setEnabled(!component->isEnabled())) { return true; @@ -871,11 +918,11 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const int row = index.row(); - if (row < 0 || row >= d->components.size()) { + if (row < 0 || row >= d->persitentData.components.size()) { return Qt::NoItemFlags; } - auto patch = d->components.at(row); + auto patch = d->persitentData.components.at(row); // TODO: this will need fine-tuning later... if(patch->canBeDisabled() && !d->interactionDisabled) { @@ -886,7 +933,7 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const int PackProfile::rowCount(const QModelIndex &parent) const { - return d->components.size(); + return d->persitentData.components.size(); } int PackProfile::columnCount(const QModelIndex &parent) const @@ -906,7 +953,7 @@ void PackProfile::move(const int index, const MoveDirection direction) theirIndex = index + 1; } - if (index < 0 || index >= d->components.size()) + if (index < 0 || index >= d->persitentData.components.size()) return; if (theirIndex >= rowCount()) theirIndex = rowCount() - 1; @@ -924,7 +971,7 @@ void PackProfile::move(const int index, const MoveDirection direction) return; } beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - d->components.swap(index, theirIndex); + d->persitentData.components.swap(index, theirIndex); endMoveRows(); invalidateLaunchProfile(); scheduleSave(); @@ -1153,7 +1200,7 @@ std::shared_ptr PackProfile::getProfile() const try { auto profile = std::make_shared(); - for(auto file: d->components) + for(auto file: d->persitentData.components) { qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); file->applyTo(profile.get()); @@ -1217,7 +1264,7 @@ void PackProfile::disableInteraction(bool disable) { if(d->interactionDisabled != disable) { d->interactionDisabled = disable; - auto size = d->components.size(); + auto size = d->persitentData.components.size(); if(size) { emit dataChanged(index(0), index(size - 1)); } diff --git a/api/logic/minecraft/PackProfile_p.h b/api/logic/minecraft/PackProfile_p.h index 6cd2a4e5..2ab8b2f6 100644 --- a/api/logic/minecraft/PackProfile_p.h +++ b/api/logic/minecraft/PackProfile_p.h @@ -5,11 +5,17 @@ #include #include #include +#include "ModpackInfo.h" class MinecraftInstance; using ComponentContainer = QList; using ComponentIndex = QMap; +struct PersistentPackProfileData { + ComponentContainer components; + PackProfileModpackInfo modpackInfo; +}; + struct PackProfileData { // the instance this belongs to @@ -30,8 +36,9 @@ struct PackProfileData return QString(); } - // persistent list of components and related machinery - ComponentContainer components; + PersistentPackProfileData persitentData; + + // temporary runtime machinery ComponentIndex componentIndex; bool dirty = false; QTimer m_saveTimer; @@ -39,4 +46,3 @@ struct PackProfileData bool loaded = false; bool interactionDisabled = true; }; - diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 38bd586b..f7e40bc9 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -84,6 +84,8 @@ SET(MULTIMC_SOURCES pages/instance/ResourcePackPage.h pages/instance/ModFolderPage.cpp pages/instance/ModFolderPage.h + pages/instance/ModpackPage.cpp + pages/instance/ModpackPage.h pages/instance/NotesPage.cpp pages/instance/NotesPage.h pages/instance/LogPage.cpp @@ -238,6 +240,7 @@ SET(MULTIMC_UIS pages/instance/GameOptionsPage.ui pages/instance/VersionPage.ui pages/instance/ModFolderPage.ui + pages/instance/ModpackPage.ui pages/instance/LogPage.ui pages/instance/InstanceSettingsPage.ui pages/instance/NotesPage.ui diff --git a/application/pages/instance/ModpackPage.cpp b/application/pages/instance/ModpackPage.cpp new file mode 100644 index 00000000..e4ca2fe4 --- /dev/null +++ b/application/pages/instance/ModpackPage.cpp @@ -0,0 +1,148 @@ +#include "ModpackPage.h" +#include "ui_ModpackPage.h" + +#include +#include +#include + +#include "dialogs/VersionSelectDialog.h" +#include "JavaCommon.h" +#include "MultiMC.h" + +#include +#include "minecraft/ModpackInfo.h" + + +ModpackPage::ModpackPage(BaseInstance *inst, QWidget *parent) + : QWidget(parent), ui(new Ui::ModpackPage), m_instance(inst) +{ + ui->setupUi(this); + connect(ui->activateUIButton, &QCommandLinkButton::clicked, this, &ModpackPage::activateUIClicked); + loadSettings(); +} + +bool ModpackPage::shouldDisplay() const +{ + return !m_instance->isRunning(); +} + +ModpackPage::~ModpackPage() +{ + delete ui; +} + +void ModpackPage::activateUIClicked(bool) +{ + +} + +bool ModpackPage::apply() +{ + applySettings(); + return true; +} + +void ModpackPage::applySettings() +{ + PackProfileModpackInfo out; + out.hasValue = ui->modpackCheck->isChecked(); + // FIXME: add a layer of indirection here + if(out.hasValue) { + out.platform = ui->platformComboBox->currentText(); + if(ui->javaArgumentsGroupBox->isChecked()) { + out.recommendedArgs = ui->jvmArgsTextBox->toPlainText(); + } + if(ui->repositoryCheck->isChecked()) { + out.repository = ui->repositoryEdit->text(); + } + if(ui->coordinateCheck->isChecked()) { + out.coordinate = ui->coordinateEdit->text(); + } + if(ui->maxMemCheck->isChecked()) { + out.maxHeap = ui->maxMemSpinBox->value(); + } + if(ui->mimMemCheck->isChecked()) { + out.minHeap = ui->minMemSpinBox->value(); + } + if(ui->optMemCheck->isChecked()) { + out.optimalHeap = ui->optMemSpinBox->value(); + } + } + +/* +struct PackProfileModpackInfo +{ + operator bool() const { + return hasValue; + } + bool hasValue = false; + + QString platform; + QString repository; + QString coordinate; + + MaybeInt minHeap; + MaybeInt optimalHeap; + MaybeInt maxHeap; + QString recommendedArgs; +}; +*/ +} + +void ModpackPage::loadSettings() +{ + PackProfileModpackInfo in; + + // Console + ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); + ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); + ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool()); + ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool()); + + // Window Size + ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool()); + ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool()); + ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt()); + ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt()); + + // Memory + ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool()); + int min = m_settings->get("MinMemAlloc").toInt(); + int max = m_settings->get("MaxMemAlloc").toInt(); + if(min < max) + { + ui->minMemSpinBox->setValue(min); + ui->maxMemSpinBox->setValue(max); + } + else + { + ui->minMemSpinBox->setValue(max); + ui->maxMemSpinBox->setValue(min); + } + ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); + bool permGenVisible = m_settings->get("PermGenVisible").toBool(); + ui->permGenSpinBox->setVisible(permGenVisible); + ui->labelPermGen->setVisible(permGenVisible); + ui->labelPermgenNote->setVisible(permGenVisible); + + + // Java Settings + bool overrideJava = m_settings->get("OverrideJava").toBool(); + bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava; + bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava; + + ui->javaSettingsGroupBox->setChecked(overrideLocation); + ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString()); + + ui->javaArgumentsGroupBox->setChecked(overrideArgs); + ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString()); + + // Custom commands + ui->customCommands->initialize( + true, + m_settings->get("OverrideCommands").toBool(), + m_settings->get("PreLaunchCommand").toString(), + m_settings->get("WrapperCommand").toString(), + m_settings->get("PostExitCommand").toString() + ); +} diff --git a/application/pages/instance/ModpackPage.h b/application/pages/instance/ModpackPage.h new file mode 100644 index 00000000..e50316fd --- /dev/null +++ b/application/pages/instance/ModpackPage.h @@ -0,0 +1,70 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "java/JavaChecker.h" +#include "BaseInstance.h" +#include +#include "pages/BasePage.h" +#include "MultiMC.h" + +namespace Ui +{ +class ModpackPage; +} + +class ModpackPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit ModpackPage(BaseInstance *inst, QWidget *parent = 0); + virtual ~ModpackPage(); + virtual QString displayName() const override + { + return tr("Modpack"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("modpack"); + } + virtual QString id() const override + { + return "modpack"; + } + virtual bool apply() override; + virtual QString helpPage() const override + { + return "Modpack-settings"; + } + virtual bool shouldDisplay() const override; + +private slots: + void on_javaDetectBtn_clicked(); + void on_javaTestBtn_clicked(); + void on_javaBrowseBtn_clicked(); + + void applySettings(); + void loadSettings(); + + void activateUIClicked(bool checked); + +private: + Ui::ModpackPage *ui; + BaseInstance *m_instance; +}; diff --git a/application/pages/instance/ModpackPage.ui b/application/pages/instance/ModpackPage.ui new file mode 100644 index 00000000..216db22b --- /dev/null +++ b/application/pages/instance/ModpackPage.ui @@ -0,0 +1,253 @@ + + + ModpackPage + + + + 0 + 0 + 779 + 781 + + + + + + + QTabWidget::Rounded + + + 0 + + + + Modpack + + + + + + + + + Repository + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Instance is based on a modpack + + + + + + + Modpack Platform + + + platformComboBox + + + + + + + Coordinate + + + + + + + + + + Qt::Horizontal + + + + + + + + Java + + + + + + true + + + Memor&y + + + false + + + + + + Minimum memory allocation: + + + + + + + Optimal memory allocation: + + + + + + + The amount of memory Minecraft is started with. + + + MB + + + 128 + + + 65536 + + + 128 + + + 1024 + + + + + + + Maximum memory allocation: + + + + + + + The maximum amount of memory Minecraft is allowed to use. + + + MB + + + 128 + + + 65536 + + + 128 + + + 1024 + + + + + + + The amount of memory available to store loaded Java classes. + + + MB + + + 64 + + + 999999999 + + + 8 + + + 1024 + + + + + + + + + + true + + + Java argumen&ts + + + true + + + false + + + + + + + + + + + + + + + + Click here to unlock + + + These are the modpack defaults and shouldn't be changed unless you are working on the modpack or want to change its source or target version, or work on it. + + + + + + + activateUIButton + settingsTabs + modpackCheck + platformComboBox + repositoryCheck + repositoryEdit + coordinateCheck + coordinateEdit + mimMemCheck + minMemSpinBox + maxMemCheck + maxMemSpinBox + optMemCheck + optMemSpinBox + javaArgumentsGroupBox + jvmArgsTextBox + + + + diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp index 1e9f9dbb..8d80bb17 100644 --- a/application/pages/modplatform/twitch/TwitchPage.cpp +++ b/application/pages/modplatform/twitch/TwitchPage.cpp @@ -101,7 +101,10 @@ void TwitchPage::suggestCurrent() dialog->setSuggestedPack(); } - dialog->setSuggestedPack(current.name, new InstanceImportTask(current.latestFile.downloadUrl)); + dialog->setSuggestedPack( + current.name, + new InstanceImportTask(current.latestFile.downloadUrl) + ); QString editedLogoName; editedLogoName = "twitch_" + current.logoName.section(".", 0, 0); model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)