diff --git a/CMakeLists.txt b/CMakeLists.txt index 366aad2..3d1520d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.25 FATAL_ERROR) # Create project project(sdk_launcher DESCRIPTION "An SDK launcher for Strata Source engine games" - VERSION "1.0.1" + VERSION "1.1.0" HOMEPAGE_URL "https://github.com/StrataSource/sdk-launcher") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index bbf5d00..4cc0a38 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,12 @@ Here is an example config file that may be loaded into the SDK launcher. "window_height": 300, // Optional, holds the download URL of the mod template for the game (must point to a zip file) // For reference, this is the P2CE template mod download URL: - "mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + //"mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + // OR it can be a JSON object if there are multiple mod templates: + "mod_template_url": { + "Full Support (HL2/P1/P2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + "Lite Support (Portal 2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/feat/lite.zip" + }, // Optional, the default is false (enables P2CE-style addons) "supports_p2ce_addons": false, // Sections hold titled groups of buttons diff --git a/res/config/p2ce.json b/res/config/p2ce.json index 4e61e81..a4909f2 100644 --- a/res/config/p2ce.json +++ b/res/config/p2ce.json @@ -2,7 +2,10 @@ "game_default": "p2ce", "window_width": 308, "window_height": 621, - "mod_template_url": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + "mod_template_url": { + "Full Support (HL2/P1/P2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/main.zip", + "Lite Support (Portal 2)": "https://github.com/StrataSource/p2ce-mod-template/archive/refs/heads/feat/lite.zip" + }, "supports_p2ce_addons": true, "sections": [ { diff --git a/src/GameConfig.cpp b/src/GameConfig.cpp index 5026570..1ee4409 100644 --- a/src/GameConfig.cpp +++ b/src/GameConfig.cpp @@ -29,6 +29,9 @@ GameConfig::OS GameConfig::osFromString(const QString& string) { if (string.contains("linux")) { out |= static_cast(LINUX); } + if (string.contains("macos")) { + out |= static_cast(MACOS); + } if (out == static_cast(NONE)) { out = static_cast(ALL); } @@ -76,8 +79,17 @@ std::optional GameConfig::parse(const QString& path) { gameConfig.windowHeight = configObject["window_height"].toInt(DEFAULT_WINDOW_HEIGHT); } - if (configObject.contains("mod_template_url") && configObject["mod_template_url"].isString()) { - gameConfig.modTemplateURL = configObject["mod_template_url"].toString(); + if (configObject.contains("mod_template_url")) { + if (configObject["mod_template_url"].isString()) { + gameConfig.modTemplateURL["Mod Template"] = configObject["mod_template_url"].toString(); + } else if (configObject["mod_template_url"].isObject()) { + for ( + const auto& modTemplateObject = configObject["mod_template_url"].toObject().toVariantMap(); + const auto& [desc, url] : modTemplateObject.asKeyValueRange() + ) { + gameConfig.modTemplateURL[desc] = url.toString(); + } + } } if (configObject.contains("supports_p2ce_addons") && configObject["supports_p2ce_addons"].isBool()) { diff --git a/src/GameConfig.h b/src/GameConfig.h index 8d50277..16b5cf4 100644 --- a/src/GameConfig.h +++ b/src/GameConfig.h @@ -2,6 +2,7 @@ #include #include +#include #include constexpr int DEFAULT_WINDOW_WIDTH = 256; @@ -22,7 +23,8 @@ class GameConfig { NONE = 0, WINDOWS = 1 << 0, LINUX = 1 << 1, - ALL = WINDOWS | LINUX, + MACOS = 1 << 2, + ALL = WINDOWS | LINUX | MACOS, }; [[nodiscard]] static OS osFromString(const QString& string); @@ -52,7 +54,7 @@ class GameConfig { [[nodiscard]] int getWindowHeight() const { return this->windowHeight; } - [[nodiscard]] const QString& getModTemplateURL() const { return this->modTemplateURL; } + [[nodiscard]] const QMap& getModTemplateURL() const { return this->modTemplateURL; } [[nodiscard]] bool supportsP2CEAddons() const { return this->p2ceAddonsSupported; } @@ -66,7 +68,7 @@ class GameConfig { bool usesLegacyBinDir = false; int windowWidth = DEFAULT_WINDOW_WIDTH; int windowHeight = DEFAULT_WINDOW_HEIGHT; - QString modTemplateURL; + QMap modTemplateURL; bool p2ceAddonsSupported = false; QList
sections; diff --git a/src/NewModDialog.cpp b/src/NewModDialog.cpp index 76a3dfe..2f0f643 100644 --- a/src/NewModDialog.cpp +++ b/src/NewModDialog.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include "Steam.h" @@ -155,10 +157,25 @@ NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* par this->parentFolder->addItem(tr("Custom Location")); layout->addRow(tr("Install Location"), this->parentFolder); - auto* parentFolderCustomLabel = new QLabel{tr("Custom Location"), this}; - this->parentFolderCustom = new QLineEdit{this}; - this->parentFolderCustom->setPlaceholderText(tr("path/to/mod/parent/folder")); - layout->addRow(parentFolderCustomLabel, this->parentFolderCustom); + auto* parentFolderCustomParent = new QWidget{this}; + auto* parentFolderCustomLayout = new QHBoxLayout{parentFolderCustomParent}; + parentFolderCustomLayout->setSpacing(4); + parentFolderCustomLayout->setContentsMargins(0, 0, 0, 0); + + this->parentFolderCustomPath = new QLineEdit{parentFolderCustomParent}; + parentFolderCustomLayout->addWidget(this->parentFolderCustomPath); + + auto* parentFolderCustomSearch = new QPushButton{parentFolderCustomParent}; + parentFolderCustomSearch->setIcon(this->style()->standardIcon(QStyle::SP_DirOpenIcon)); + parentFolderCustomLayout->addWidget(parentFolderCustomSearch); + + QObject::connect(parentFolderCustomSearch, &QPushButton::clicked, this, [this] { + if (const auto path = QFileDialog::getExistingDirectory(this, tr("Select Parent Folder")); !path.isEmpty()) { + this->parentFolderCustomPath->setText(path); + } + }); + + layout->addRow(tr("Custom Location"), parentFolderCustomParent); this->modID = new QLineEdit{this}; this->modID->setPlaceholderText(tr("For example: p2ce, revolution, portal2")); @@ -179,12 +196,10 @@ NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* par this->network = new QNetworkAccessManager{this}; // We want the custom input to be invisible unless the combo box is on the custom option - parentFolderCustomLabel->hide(); - this->parentFolderCustom->hide(); - QObject::connect(this->parentFolder, &QComboBox::currentIndexChanged, this, [this, knowsSourcemodsDirLocation, parentFolderCustomLabel](int index) { + layout->setRowVisible(parentFolderCustomParent, false); + QObject::connect(this->parentFolder, &QComboBox::currentIndexChanged, this, [knowsSourcemodsDirLocation, layout, parentFolderCustomParent](int index) { const int customIndex = knowsSourcemodsDirLocation ? 2 : 1; - parentFolderCustomLabel->setVisible(index == customIndex); - this->parentFolderCustom->setVisible(index == customIndex); + layout->setRowVisible(parentFolderCustomParent, index == customIndex); }); // Connect ok/cancel buttons to download stuff @@ -256,9 +271,9 @@ NewModDialog::NewModDialog(QString gameRoot_, QString downloadURL_, QWidget* par // If installing to sourcemods, tell user they will need to restart steam if (this->parentFolder->count() == 3 && this->parentFolder->currentIndex() == 0) { QMessageBox::information(this, tr("Info"), tr("Your mod has been installed to Steam's SourceMods folder, which means it will show up in your Steam library! This requires you to restart Steam once.")); - QDesktopServices::openUrl({QString("file:///") + modInstallDir}); } + QDesktopServices::openUrl(QUrl::fromLocalFile(modInstallDir)); this->accept(); }); }); @@ -277,7 +292,7 @@ QString NewModDialog::getModInstallDirParent() const { return this->gameRoot; default: case 2: - return this->parentFolderCustom->text(); + return this->parentFolderCustomPath->text(); } } diff --git a/src/NewModDialog.h b/src/NewModDialog.h index f9e6f4c..fe5313a 100644 --- a/src/NewModDialog.h +++ b/src/NewModDialog.h @@ -28,7 +28,7 @@ public Q_SLOTS: QString downloadURL; QComboBox* parentFolder; - QLineEdit* parentFolderCustom; + QLineEdit* parentFolderCustomPath; QLineEdit* modID; QCheckBox* addShortcutOnDesktop; QProgressBar* downloadProgress; diff --git a/src/Window.cpp b/src/Window.cpp index 8b618aa..1527515 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -129,9 +129,7 @@ Window::Window(QWidget* parent) // Utilities menu auto* utilitiesMenu = this->menuBar()->addMenu(tr("Utilities")); - this->utilities_createNewMod = utilitiesMenu->addAction(this->style()->standardIcon(QStyle::SP_FileIcon), tr("Create New Mod"), [this] { - NewModDialog::open(::getRootPath(this->configUsingLegacyBinDir), this->configModTemplateURL, this); - }); + this->utilities_createNewMod = utilitiesMenu->addMenu(this->style()->standardIcon(QStyle::SP_FileIcon), tr("Create New Mod")); this->utilities_createNewAddon = utilitiesMenu->addAction(this->style()->standardIcon(QStyle::SP_FileIcon), tr("Create New Addon"), [this] { QString gameRoot; @@ -225,7 +223,18 @@ void Window::loadGameConfig(const QString& path) { this->configUsingLegacyBinDir = gameConfig->getUsesLegacyBinDir(); this->configModTemplateURL = gameConfig->getModTemplateURL(); - this->utilities_createNewMod->setDisabled(this->configModTemplateURL.isEmpty()); + + this->utilities_createNewMod->clear(); + if (this->configModTemplateURL.isEmpty()) { + this->utilities_createNewMod->setDisabled(true); + } else { + for (const auto& [desc, url] : this->configModTemplateURL.asKeyValueRange()) { + this->utilities_createNewMod->addAction(desc, [this, url] { + NewModDialog::open(::getRootPath(this->configUsingLegacyBinDir), url, this); + }); + } + } + this->utilities_createNewAddon->setDisabled(!gameConfig->supportsP2CEAddons()); auto recentConfigs = Options::get(STR_RECENT_CONFIGS); diff --git a/src/Window.h b/src/Window.h index 5cba48c..54071ee 100644 --- a/src/Window.h +++ b/src/Window.h @@ -32,13 +32,13 @@ class Window : public QMainWindow { private: QString gameDefault; bool configUsingLegacyBinDir; - QString configModTemplateURL; + QMap configModTemplateURL; QMenu* recent; QAction* config_loadDefault; QAction* game_overrideGame; QAction* game_resetToDefault; - QAction* utilities_createNewMod; + QMenu* utilities_createNewMod; QAction* utilities_createNewAddon; QWidget* main;