Compare commits

...

5 Commits
v1.0.0 ... main

9 changed files with 899 additions and 77 deletions

View File

@ -1,17 +1,21 @@
QT += core gui widgets
QT += core widgets gui
CONFIG += c++17
RC_ICONS = app.ico
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
mainwindow.cpp
mainwindow.cpp \
splashscreen.cpp
HEADERS += \
mainwindow.h
mainwindow.h \
splashscreen.h
FORMS += \
mainwindow.ui

View File

@ -1,3 +1,34 @@
# GWF_PowerUpInjector
Ever wanted to introduce powerups into an already made GWF workshop map? Now you can with the GWF Powerup Injector. It allows you to select any downloaded workshop map, inject powerup spawners at procedural locations, and save.
Ever wanted to introduce powerups into an existing GWF Workshop map? Now you can with the **GWF PowerUp Injector**. It allows you to select any downloaded Workshop map, inject powerup spawners at procedurally chosen locations, and save.
# License / Disclaimer
GWF_PowerUpInjector is licensed under the **GNU General Public License v3.0 (GPL-3.0)**. You are free to use GWF_PowerUpInjector (and its source code) under the terms of the GPL. GWF_PowerUpInjector is distributed in the hope that it will be useful and enjoyed, but it comes **WITHOUT ANY WARRANTY**; without even the implied warranty of **MERCHANTABILITY** or **FITNESS FOR A PARTICULAR PURPOSE**. See the LICENSE file for more information.
This repository is not associated with Blacklight Interactive, Team17, or the developers. These tools are developed to allow users to enhance the already enjoyable experience of *Golf With Your Friends*.
# Instructions
1. Download the latest release of the tool.
2. Extract and run `GWF_PowerUpInjector.exe`.
3. Enter the path to your Steam directory.
- This is only needed if the tool doesn't find the Steam directory automatically.
4. Select the Workshop map from the list on the left.
5. Navigate to the **PowerUps** section and click **Generate**.
6. Load the map as usual within *Golf With Your Friends*.
# Problems and Troubleshooting
- Currently, the application appends powerups to the current map. This means clicking **Generate** multiple times will add duplicate powerups each time. If this happens and you want to revert the map, you have two options:
a) Re-download the map (unsubscribe and re-subscribe in Steam).
**OR**
b) Restore the `Map.old` file in the Steam Workshop directory
(e.g., `C:\Program Files (x86)\Steam\steamapps\workshop\content\431240\[MAP_ID]`).
# Notes
- Developed for the Steam version of *Golf With Your Friends*.

BIN
app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -1,11 +1,42 @@
#include "mainwindow.h"
#include "splashscreen.h"
#include <QApplication>
#include <QThread>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QCoreApplication::setOrganizationDomain("redline.llc");
QCoreApplication::setOrganizationName("RedLine Solutions, LLC.");
QCoreApplication::setApplicationName("GWF PowerUp Injector");
QCoreApplication::setApplicationVersion("v1.1.0");
// Show splash screen
SplashScreen splash;
splash.show();
splash.startAnimation();
a.processEvents();
// Create main window (doesn't load maps yet)
MainWindow w;
w.show();
// Connect progress signals to splash screen
QObject::connect(&w, &MainWindow::loadingProgress,
[&splash](int percent, const QString &status) {
splash.setProgress(percent);
splash.setStatus(status);
});
QObject::connect(&w, &MainWindow::loadingComplete, [&]() {
// Small delay to show 100% progress
QThread::msleep(300);
splash.finish(&w);
});
// Start async initialization (loads maps with progress reporting)
w.initializeAsync();
return a.exec();
}

View File

@ -1,6 +1,7 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QRandomGenerator>
MainWindow::MainWindow(QWidget *parent)
@ -9,22 +10,45 @@ MainWindow::MainWindow(QWidget *parent)
, mSteamPath()
{
ui->setupUi(this);
// Don't load maps here - call initializeAsync() after showing splash screen
}
void MainWindow::initializeAsync()
{
emit loadingProgress(5, "Finding Steam installation...");
QCoreApplication::processEvents();
findSteamPath();
emit loadingProgress(10, "Scanning workshop directory...");
QCoreApplication::processEvents();
QDir gwfWorkshopDir(mSteamPath + "/steamapps/workshop/content/431240");
if (!gwfWorkshopDir.exists()) { return; }
if (!gwfWorkshopDir.exists())
{
emit loadingProgress(100, "Ready (no maps found)");
emit loadingComplete();
return;
}
qDebug() << "Found Golf With Friends Workshop Path:" << gwfWorkshopDir.path();
foreach (const QString entry, gwfWorkshopDir.entryList({}, QDir::NoFilter, QDir::Time))
QStringList entries = gwfWorkshopDir.entryList({}, QDir::NoDotAndDotDot | QDir::Dirs, QDir::Time);
int totalEntries = entries.size();
int processed = 0;
foreach (const QString entry, entries)
{
if (entry == "." || entry == "..") { continue; }
// Calculate and emit progress (10-90%)
int progress = 10 + (processed * 80 / qMax(totalEntries, 1));
emit loadingProgress(progress, QString("Loading map: %1").arg(entry));
QCoreApplication::processEvents();
QFile newEntry(gwfWorkshopDir.path() + "/" + entry + "/Map");
if (!newEntry.open(QIODevice::ReadWrite))
{
qDebug() << "Failed to open workshop map:" << entry;
processed++;
continue;
}
const QByteArray rawMapData = newEntry.readAll();
@ -52,28 +76,65 @@ MainWindow::MainWindow(QWidget *parent)
newInfo.mMapData = loadObj;
mLevels.push_back(newInfo);
processed++;
}
emit loadingProgress(95, "Updating level list...");
QCoreApplication::processEvents();
updateLevels();
emit loadingProgress(100, "Ready!");
emit loadingComplete();
}
void MainWindow::findSteamPath()
{
QDir testDir("C:/Program Files (x86)/Steam");
if (testDir.exists())
// Check multiple common Steam installation locations
QStringList possiblePaths = {
"C:/Program Files (x86)/Steam",
"C:/Program Files/Steam",
"D:/Steam",
"D:/Games/Steam",
"E:/Steam",
"E:/Games/Steam"
};
for (const QString &path : possiblePaths)
{
mSteamPath = testDir.path();
QDir testDir(path);
if (testDir.exists())
{
mSteamPath = testDir.path();
break;
}
}
else
// If not found in common locations, ask user
if (mSteamPath.isEmpty())
{
mSteamPath = QFileDialog::getExistingDirectory(this, "Select Steam Directory", "C:/Program Files (x86)/", QFileDialog::ShowDirsOnly);
mSteamPath = QFileDialog::getExistingDirectory(this, "Select Steam Directory",
"C:/Program Files (x86)/", QFileDialog::ShowDirsOnly);
}
if (!mSteamPath.isEmpty())
{
ui->lineEdit_SteamPath->setText(QString("FOUND: %1").arg(mSteamPath));
// Verify the GWF workshop folder exists
QDir gwfDir(mSteamPath + "/steamapps/workshop/content/431240");
if (!gwfDir.exists())
{
ui->lineEdit_SteamPath->setText(QString("FOUND: %1 (No GWF maps)").arg(mSteamPath));
}
else
{
ui->lineEdit_SteamPath->setText(QString("FOUND: %1").arg(mSteamPath));
}
ui->lineEdit_SteamPath->setEnabled(false);
}
else
{
ui->lineEdit_SteamPath->setText("NOT FOUND - Please install Steam or GWF workshop maps");
}
}
void MainWindow::updateLevels()
@ -162,7 +223,14 @@ MainWindow::~MainWindow()
void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow)
{
LevelInfo selectedLevel = mLevels[currentRow];
// Bounds checking to prevent crashes
if (currentRow < 0 || currentRow >= mLevels.size())
{
clearMapDetails();
return;
}
const LevelInfo &selectedLevel = mLevels[currentRow];
ui->lineEdit_LevelName->setText(selectedLevel.mLevelName);
ui->lineEdit_Description->setText(selectedLevel.mDescription);
ui->lineEdit_ID->setText(selectedLevel.mPublishedID);
@ -173,6 +241,9 @@ void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow)
QPixmap scaledPreview = selectedLevel.mPreview.scaled(ui->label_MapPreview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
ui->label_MapPreview->setPixmap(scaledPreview);
// Clear the powerup list before adding new items (fixes accumulation bug)
ui->listWidget_powerups->clear();
for (int i = 0; i < selectedLevel.mPowerUps.size(); i++)
{
ui->listWidget_powerups->addItem(QString("Powerup %1").arg(i));
@ -182,8 +253,25 @@ void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow)
void MainWindow::on_listWidget_powerups_currentRowChanged(int currentRow)
{
LevelInfo selectedLevel = mLevels[ui->listWidget_MapSelect->currentRow()];
PowerUpInfo selectedPowerUp = selectedLevel.mPowerUps[currentRow];
int mapRow = ui->listWidget_MapSelect->currentRow();
// Bounds checking for map selection
if (mapRow < 0 || mapRow >= mLevels.size())
{
clearPowerupDetails();
return;
}
const LevelInfo &selectedLevel = mLevels[mapRow];
// Bounds checking for powerup selection
if (currentRow < 0 || currentRow >= selectedLevel.mPowerUps.size())
{
clearPowerupDetails();
return;
}
const PowerUpInfo &selectedPowerUp = selectedLevel.mPowerUps[currentRow];
ui->doubleSpinBox_PosX->setValue(selectedPowerUp.mPosition.x());
ui->doubleSpinBox_PosY->setValue(selectedPowerUp.mPosition.y());
@ -204,20 +292,26 @@ void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup)
const QString mapPath = aLevelInfo.mMapPath;
if (!QFile::exists(mapPath))
{
qDebug() << "File doesn't exist: " << mapPath;
QMessageBox::critical(this, "Save Error",
QString("Map file not found:\n%1").arg(mapPath));
return;
}
const QString backupMapPath = aLevelInfo.mMapPath + ".old";
if (!QFile::exists(backupMapPath) && aBackup)
{
QFile::copy(mapPath, backupMapPath);
if (!QFile::copy(mapPath, backupMapPath))
{
QMessageBox::warning(this, "Backup Warning",
"Could not create backup file. Proceeding without backup.");
}
}
QFile backupMapFile(backupMapPath);
if (!backupMapFile.open(QIODevice::ReadOnly))
{
qDebug() << "Failed to open backup file: " << mapPath;
QMessageBox::critical(this, "Save Error",
QString("Failed to open backup file for reading:\n%1").arg(backupMapPath));
return;
}
@ -227,16 +321,16 @@ void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup)
QFile mapFile(mapPath);
if (!mapFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
{
qDebug() << "Failed to open file: " << mapPath;
QMessageBox::critical(this, "Save Error",
QString("Failed to open map file for writing:\n%1").arg(mapPath));
return;
}
QByteArray powerUpsData = ",";
for (int i = 0; i < aLevelInfo.mPowerUps.size(); i++)
{
PowerUpInfo powerUp = aLevelInfo.mPowerUps[i];
const PowerUpInfo &powerUp = aLevelInfo.mPowerUps[i];
const QString powerUpData = QString("{\"sType\":%1,\"pX\":%2,\"pY\":%3,\"pZ\":%4,\"rW\":%5,\"rX\":%6,\"rY\":%7,\"rZ\":%8,\"sX\":%9,\"sY\":%10,\"sZ\":%11,\"obName\":\"Powerup Spawner\",\"photonData\":{\"photonViewID\":[%12]}},")
.arg(0)
.arg(powerUp.mPosition.x(), 0, 'f', 1)
@ -261,19 +355,69 @@ void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup)
mapData.append(powerUpsData);
mapData.append(backupData.mid(injectionPoint));
mapFile.write(mapData);
if (mapFile.write(mapData) == -1)
{
QMessageBox::critical(this, "Save Error",
QString("Failed to write map data:\n%1").arg(mapPath));
mapFile.close();
return;
}
mapFile.close();
qDebug() << "Map saved successfully:" << mapPath;
}
void MainWindow::on_pushButton_Generate_clicked()
{
int currentListRow = ui->listWidget_MapSelect->currentRow();
// Bounds check
if (currentListRow < 0 || currentListRow >= mLevels.size())
{
QMessageBox::warning(this, "No Map Selected",
"Please select a map before generating powerups.");
return;
}
LevelInfo selectedLevel = mLevels[currentListRow];
// Check if powerups already exist and ask user what to do
if (!selectedLevel.mPowerUps.isEmpty())
{
QMessageBox::StandardButton reply = QMessageBox::question(this,
"Existing Powerups Found",
QString("This map already has %1 powerup(s).\n\n"
"Do you want to replace them with new generated powerups?\n\n"
"Click 'Yes' to replace, 'No' to add to existing, or 'Cancel' to abort.")
.arg(selectedLevel.mPowerUps.size()),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::Cancel);
if (reply == QMessageBox::Cancel)
{
return;
}
if (reply == QMessageBox::Yes)
{
selectedLevel.mPowerUps.clear();
}
// If No, keep existing and add new ones
}
QVector<QVector3D> shuffledPositions = selectedLevel.mObjectPositions;
if (shuffledPositions.isEmpty())
{
QMessageBox::warning(this, "No Base Objects",
"This map has no base objects to use as spawn positions.\n"
"Powerups are generated near objects with 'base' in their name.");
return;
}
auto rng = QRandomGenerator::global();
std::shuffle(shuffledPositions.begin(), shuffledPositions.end(), *rng);
int generatedCount = 0;
for (int i = 0; i < shuffledPositions.size(); i++)
{
if (i == MAX_POWERUPS)
@ -295,14 +439,246 @@ void MainWindow::on_pushButton_Generate_clicked()
PowerUpInfo newPowerUp;
newPowerUp.mPosition = QVector3D(pos.x(), pos.y() + 0.25, pos.z());
newPowerUp.mRotation = QVector4D(0, 0, 0, 1); // Identity quaternion
newPowerUp.mScale = QVector3D(1, 1, 1);
selectedLevel.mPowerUps.push_back(newPowerUp);
generatedCount++;
}
}
saveMap(selectedLevel, true);
mLevels[ui->listWidget_MapSelect->currentRow()] = selectedLevel;
mLevels[currentListRow] = selectedLevel;
ui->listWidget_MapSelect->setCurrentRow(currentListRow);
// Refresh the UI to show the new powerups
ui->listWidget_MapSelect->setCurrentRow(-1); // Deselect first
ui->listWidget_MapSelect->setCurrentRow(currentListRow); // Reselect to refresh
ui->statusBar->showMessage(QString("Generated %1 powerups successfully!").arg(generatedCount), 5000);
}
void MainWindow::clearMapDetails()
{
ui->lineEdit_LevelName->clear();
ui->lineEdit_Description->clear();
ui->lineEdit_ID->clear();
ui->spinBox_Music->setValue(0);
ui->spinBox_Skybox->setValue(0);
ui->spinBox_PowerupCount->setValue(0);
ui->label_MapPreview->clear();
ui->listWidget_powerups->clear();
clearPowerupDetails();
}
void MainWindow::clearPowerupDetails()
{
ui->doubleSpinBox_PosX->setValue(0);
ui->doubleSpinBox_PosY->setValue(0);
ui->doubleSpinBox_PosZ->setValue(0);
ui->doubleSpinBox_RotW->setValue(0);
ui->doubleSpinBox_RotX->setValue(0);
ui->doubleSpinBox_RotY->setValue(0);
ui->doubleSpinBox_RotZ->setValue(0);
ui->doubleSpinBox_ScaleX->setValue(0);
ui->doubleSpinBox_ScaleY->setValue(0);
ui->doubleSpinBox_ScaleZ->setValue(0);
}
void MainWindow::refreshPowerupList(int mapRow)
{
if (mapRow < 0 || mapRow >= mLevels.size())
{
ui->listWidget_powerups->clear();
return;
}
ui->listWidget_powerups->clear();
const LevelInfo &level = mLevels[mapRow];
for (int i = 0; i < level.mPowerUps.size(); i++)
{
ui->listWidget_powerups->addItem(QString("Powerup %1").arg(i));
}
ui->spinBox_PowerupCount->setValue(level.mPowerUps.size());
}
void MainWindow::on_pushButton_SavePowerUp_clicked()
{
int mapRow = ui->listWidget_MapSelect->currentRow();
int powerupRow = ui->listWidget_powerups->currentRow();
if (mapRow < 0 || mapRow >= mLevels.size())
{
QMessageBox::warning(this, "No Map Selected",
"Please select a map first.");
return;
}
if (powerupRow < 0 || powerupRow >= mLevels[mapRow].mPowerUps.size())
{
QMessageBox::warning(this, "No Powerup Selected",
"Please select a powerup to save.");
return;
}
// Read values from UI and update the powerup
PowerUpInfo &powerup = mLevels[mapRow].mPowerUps[powerupRow];
powerup.mPosition.setX(ui->doubleSpinBox_PosX->value());
powerup.mPosition.setY(ui->doubleSpinBox_PosY->value());
powerup.mPosition.setZ(ui->doubleSpinBox_PosZ->value());
powerup.mRotation.setW(ui->doubleSpinBox_RotW->value());
powerup.mRotation.setX(ui->doubleSpinBox_RotX->value());
powerup.mRotation.setY(ui->doubleSpinBox_RotY->value());
powerup.mRotation.setZ(ui->doubleSpinBox_RotZ->value());
powerup.mScale.setX(ui->doubleSpinBox_ScaleX->value());
powerup.mScale.setY(ui->doubleSpinBox_ScaleY->value());
powerup.mScale.setZ(ui->doubleSpinBox_ScaleZ->value());
// Save to disk
saveMap(mLevels[mapRow], true);
ui->statusBar->showMessage("Powerup saved successfully!", 3000);
}
void MainWindow::on_pushButton_NewPowerUp_clicked()
{
int mapRow = ui->listWidget_MapSelect->currentRow();
if (mapRow < 0 || mapRow >= mLevels.size())
{
QMessageBox::warning(this, "No Map Selected",
"Please select a map before adding a powerup.");
return;
}
// Create new powerup at origin with default values
PowerUpInfo newPowerup;
newPowerup.mPosition = QVector3D(0, 0.25, 0);
newPowerup.mRotation = QVector4D(0, 0, 0, 1); // Identity quaternion
newPowerup.mScale = QVector3D(1, 1, 1);
mLevels[mapRow].mPowerUps.append(newPowerup);
// Refresh the powerup list
refreshPowerupList(mapRow);
// Select the new powerup
ui->listWidget_powerups->setCurrentRow(mLevels[mapRow].mPowerUps.size() - 1);
ui->statusBar->showMessage("New powerup added. Edit values and click Save.", 3000);
}
void MainWindow::on_pushButton_DeletePowerUp_clicked()
{
int mapRow = ui->listWidget_MapSelect->currentRow();
int powerupRow = ui->listWidget_powerups->currentRow();
if (mapRow < 0 || mapRow >= mLevels.size())
{
QMessageBox::warning(this, "No Map Selected",
"Please select a map first.");
return;
}
if (powerupRow < 0 || powerupRow >= mLevels[mapRow].mPowerUps.size())
{
QMessageBox::warning(this, "No Powerup Selected",
"Please select a powerup to delete.");
return;
}
QMessageBox::StandardButton reply = QMessageBox::question(this,
"Confirm Delete",
QString("Are you sure you want to delete Powerup %1?").arg(powerupRow),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (reply == QMessageBox::Yes)
{
mLevels[mapRow].mPowerUps.remove(powerupRow);
// Save to disk
saveMap(mLevels[mapRow], true);
// Refresh powerup list
refreshPowerupList(mapRow);
clearPowerupDetails();
ui->statusBar->showMessage("Powerup deleted successfully!", 3000);
}
}
void MainWindow::on_pushButton_Restore_clicked()
{
int mapRow = ui->listWidget_MapSelect->currentRow();
if (mapRow < 0 || mapRow >= mLevels.size())
{
QMessageBox::warning(this, "No Map Selected",
"Please select a map to restore.");
return;
}
QString backupPath = mLevels[mapRow].mMapPath + ".old";
if (!QFile::exists(backupPath))
{
QMessageBox::information(this, "No Backup",
"No backup file exists for this map.\n"
"Backups are created automatically when you generate or modify powerups.");
return;
}
QMessageBox::StandardButton reply = QMessageBox::question(this,
"Restore Backup",
"This will restore the map to its original state before any modifications.\n\n"
"All powerup changes will be lost. Are you sure you want to continue?",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (reply == QMessageBox::Yes)
{
QString mapPath = mLevels[mapRow].mMapPath;
// Remove current map file
if (!QFile::remove(mapPath))
{
QMessageBox::critical(this, "Restore Error",
"Failed to remove current map file.");
return;
}
// Copy backup to map file
if (!QFile::copy(backupPath, mapPath))
{
QMessageBox::critical(this, "Restore Error",
"Failed to restore backup file.");
return;
}
// Reload the map data
QFile mapFile(mapPath);
if (mapFile.open(QIODevice::ReadOnly))
{
const QByteArray rawMapData = mapFile.readAll();
size_t headerSize = rawMapData.indexOf('{');
size_t footerSize = rawMapData.size() - rawMapData.lastIndexOf('}') - 1;
size_t payloadSize = rawMapData.size() - headerSize - footerSize;
const QByteArray mapData = rawMapData.mid(headerSize, payloadSize);
QJsonDocument loadDoc = QJsonDocument::fromJson(mapData);
QJsonObject loadObj = loadDoc.object();
mLevels[mapRow].mPowerUps = findPowerUps(loadObj);
mLevels[mapRow].mMaxViewID = getMaxViewID(loadObj);
mLevels[mapRow].mMapData = loadObj;
mapFile.close();
}
// Refresh UI
refreshPowerupList(mapRow);
clearPowerupDetails();
ui->statusBar->showMessage("Map restored from backup successfully!", 3000);
}
}

View File

@ -8,12 +8,16 @@
#include <QDir>
#include <QFileDialog>
#include <QVector3D>
#include <QVector4D>
#include <QPixmap>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonObject>
#include <QMessageBox>
struct PowerUpInfo
{
QVector3D mPosition;
@ -53,22 +57,34 @@ public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void initializeAsync(); // Async initialization for splash screen
void findSteamPath();
void updateLevels();
QVector<PowerUpInfo> findPowerUps(QJsonObject aMapData);
QVector<QVector3D> getObjectPositions(QJsonObject aObj);
void saveMap(LevelInfo aLevelInfo, bool aBackup = true);
QByteArray serializePowerUpCompact(const PowerUpInfo &p, int photonId);
int getMaxViewID(QJsonObject aMapData);
signals:
void loadingProgress(int percent, const QString &status);
void loadingComplete();
private slots:
void on_listWidget_MapSelect_currentRowChanged(int currentRow);
void on_listWidget_powerups_currentRowChanged(int currentRow);
void on_pushButton_Generate_clicked();
void on_pushButton_SavePowerUp_clicked();
void on_pushButton_NewPowerUp_clicked();
void on_pushButton_DeletePowerUp_clicked();
void on_pushButton_Restore_clicked();
private:
void clearMapDetails();
void clearPowerupDetails();
void refreshPowerupList(int mapRow);
Ui::MainWindow *ui;
QString mSteamPath;

View File

@ -17,7 +17,7 @@
</size>
</property>
<property name="windowTitle">
<string>MainWindow</string>
<string>Golf With Your Friends PowerUp Injector</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
@ -252,9 +252,6 @@
</item>
<item row="1" column="1" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_PosX">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -275,9 +272,6 @@
</item>
<item row="1" column="4" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_PosY">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -298,9 +292,6 @@
</item>
<item row="1" column="7" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_PosZ">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -321,9 +312,6 @@
</item>
<item row="3" column="10">
<widget class="QDoubleSpinBox" name="doubleSpinBox_RotZ">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -344,9 +332,6 @@
</item>
<item row="5" column="1" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_ScaleX">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -367,9 +352,6 @@
</item>
<item row="5" column="4" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_ScaleY">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -390,9 +372,6 @@
</item>
<item row="5" column="7" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_ScaleZ">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -434,9 +413,6 @@
</item>
<item row="3" column="7" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_RotY">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -457,9 +433,6 @@
</item>
<item row="3" column="1" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_RotW">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -480,9 +453,6 @@
</item>
<item row="3" column="4" colspan="2">
<widget class="QDoubleSpinBox" name="doubleSpinBox_RotX">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
@ -513,9 +483,6 @@
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QPushButton" name="pushButton_SavePowerUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Save Powerup</string>
</property>
@ -523,14 +490,18 @@
</item>
<item>
<widget class="QPushButton" name="pushButton_NewPowerUp">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>New Powerup</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_DeletePowerUp">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
@ -547,6 +518,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_Restore">
<property name="text">
<string>Restore Backup</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_Generate">
<property name="text">
@ -588,17 +566,7 @@
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1330</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<resources/>
<connections/>

338
splashscreen.cpp Normal file
View File

@ -0,0 +1,338 @@
#include "splashscreen.h"
#include <QPainter>
#include <QPainterPath>
#include <QPixmap>
#include <QApplication>
#include <QScreen>
#include <QCoreApplication>
#include <QDate>
#include <QtMath>
#include <QLinearGradient>
#include <QRadialGradient>
SplashScreen::SplashScreen(QWidget *parent)
: QSplashScreen()
, mAnimationTimer(new QTimer(this))
{
Q_UNUSED(parent);
// Create transparent pixmap
QPixmap pixmap(WIDTH, HEIGHT);
pixmap.fill(Qt::transparent);
setPixmap(pixmap);
setWindowFlags(Qt::SplashScreen | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
setAttribute(Qt::WA_TranslucentBackground);
// Center on screen
if (QScreen *screen = QApplication::primaryScreen()) {
QRect screenGeometry = screen->geometry();
int x = (screenGeometry.width() - WIDTH) / 2;
int y = (screenGeometry.height() - HEIGHT) / 2;
move(x, y);
}
// Connect animation timer
connect(mAnimationTimer, &QTimer::timeout, this, &SplashScreen::onAnimationTick);
}
SplashScreen::~SplashScreen()
{
stopAnimation();
}
void SplashScreen::setStatus(const QString &message)
{
mStatus = message;
repaint();
QApplication::processEvents();
}
void SplashScreen::setProgress(int value, int max)
{
mProgress = value;
mProgressMax = max;
repaint();
QApplication::processEvents();
}
void SplashScreen::startAnimation()
{
mAnimationTimer->start(16); // ~60 FPS
}
void SplashScreen::stopAnimation()
{
mAnimationTimer->stop();
}
void SplashScreen::finish(QWidget *mainWindow)
{
stopAnimation();
if (mainWindow) {
mainWindow->show();
}
close();
}
void SplashScreen::onAnimationTick()
{
mAnimationPhase += 0.08f;
if (mAnimationPhase > 2 * M_PI) {
mAnimationPhase -= 2 * M_PI;
}
repaint();
}
void SplashScreen::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event);
// Allow clicking to dismiss during loading if desired
}
void SplashScreen::keyPressEvent(QKeyEvent *event)
{
Q_UNUSED(event);
// Allow key press to dismiss during loading if desired
}
void SplashScreen::drawContents(QPainter *painter)
{
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
drawBackground(painter);
drawPowerupIcons(painter);
drawTitle(painter);
// Draw bouncing golf ball
int ballX = 400;
int ballY = 85 + static_cast<int>(qSin(mAnimationPhase) * 15);
drawGolfBall(painter, ballX, ballY, 25);
drawProgressBar(painter);
drawStatusText(painter);
}
void SplashScreen::drawBackground(QPainter *painter)
{
// Create clipping path for rounded corners
QPainterPath clipPath;
clipPath.addRoundedRect(0, 0, WIDTH, HEIGHT, 20, 20);
painter->setClipPath(clipPath);
// Draw gradient background (golf course green)
QLinearGradient gradient(0, 0, 0, HEIGHT);
gradient.setColorAt(0, mGreenLight);
gradient.setColorAt(1, mGreenDark);
painter->setBrush(gradient);
painter->setPen(Qt::NoPen);
painter->drawRect(0, 0, WIDTH, HEIGHT);
// Draw subtle grass texture lines
painter->setPen(QPen(mGreenDark.darker(110), 1));
for (int y = 20; y < HEIGHT - 60; y += 12) {
int offset = static_cast<int>(qSin(y * 0.1f + mAnimationPhase * 0.5f) * 3);
painter->drawLine(10 + offset, y, WIDTH - 10 + offset, y);
}
// Draw decorative border
painter->setClipping(false);
painter->setPen(QPen(mGreenDark.darker(130), 3));
painter->setBrush(Qt::NoBrush);
painter->drawRoundedRect(1, 1, WIDTH - 2, HEIGHT - 2, 20, 20);
}
void SplashScreen::drawGolfBall(QPainter *painter, int x, int y, int radius)
{
// Draw shadow
painter->setBrush(QColor(0, 0, 0, 50));
painter->setPen(Qt::NoPen);
painter->drawEllipse(x - radius + 5, y - radius + 8, radius * 2, radius * 2 - 5);
// Draw golf ball with gradient
QRadialGradient ballGradient(x - radius/3, y - radius/3, radius * 1.5);
ballGradient.setColorAt(0, QColor(255, 255, 255));
ballGradient.setColorAt(0.7, QColor(240, 240, 240));
ballGradient.setColorAt(1, QColor(200, 200, 200));
painter->setBrush(ballGradient);
painter->setPen(QPen(QColor(180, 180, 180), 1));
painter->drawEllipse(x - radius, y - radius, radius * 2, radius * 2);
// Draw dimples
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(180, 180, 180, 100));
int dimpleSize = 4;
// Top row
painter->drawEllipse(x - 8, y - 12, dimpleSize, dimpleSize);
painter->drawEllipse(x + 4, y - 12, dimpleSize, dimpleSize);
// Middle row
painter->drawEllipse(x - 12, y - 4, dimpleSize, dimpleSize);
painter->drawEllipse(x, y - 4, dimpleSize, dimpleSize);
painter->drawEllipse(x + 8, y - 4, dimpleSize, dimpleSize);
// Bottom row
painter->drawEllipse(x - 8, y + 4, dimpleSize, dimpleSize);
painter->drawEllipse(x + 4, y + 4, dimpleSize, dimpleSize);
}
void SplashScreen::drawPowerupIcons(QPainter *painter)
{
painter->setClipRect(0, 0, WIDTH, HEIGHT);
// Draw scattered powerup icons around the edges
// Lightning bolt - top left
painter->setPen(Qt::NoPen);
painter->setBrush(QColor("#FFEB3B")); // Yellow
QPainterPath lightning;
lightning.moveTo(60, 30);
lightning.lineTo(75, 30);
lightning.lineTo(65, 50);
lightning.lineTo(80, 50);
lightning.lineTo(55, 80);
lightning.lineTo(65, 55);
lightning.lineTo(50, 55);
lightning.closeSubpath();
painter->drawPath(lightning);
// Star - bottom left
painter->setBrush(QColor("#FF9800")); // Orange
QPainterPath star;
int starX = 50, starY = 230;
for (int i = 0; i < 5; i++) {
double angle = i * 72 * M_PI / 180 - M_PI / 2;
double innerAngle = angle + 36 * M_PI / 180;
if (i == 0) {
star.moveTo(starX + 15 * qCos(angle), starY + 15 * qSin(angle));
} else {
star.lineTo(starX + 15 * qCos(angle), starY + 15 * qSin(angle));
}
star.lineTo(starX + 7 * qCos(innerAngle), starY + 7 * qSin(innerAngle));
}
star.closeSubpath();
painter->drawPath(star);
// Speed lines - right side
painter->setPen(QPen(QColor("#03A9F4"), 3, Qt::SolidLine, Qt::RoundCap)); // Blue
painter->drawLine(420, 180, 460, 180);
painter->drawLine(425, 190, 470, 190);
painter->drawLine(420, 200, 460, 200);
// Circle powerup - top right area
QRadialGradient circleGrad(440, 35, 12);
circleGrad.setColorAt(0, QColor("#E91E63")); // Pink
circleGrad.setColorAt(1, QColor("#C2185B"));
painter->setBrush(circleGrad);
painter->setPen(Qt::NoPen);
painter->drawEllipse(428, 23, 24, 24);
// Small decorative dots
painter->setBrush(QColor(255, 255, 255, 100));
painter->drawEllipse(90, 140, 6, 6);
painter->drawEllipse(380, 250, 8, 8);
painter->drawEllipse(120, 200, 5, 5);
}
void SplashScreen::drawTitle(QPainter *painter)
{
painter->setClipRect(0, 0, WIDTH, HEIGHT);
// Draw drop shadow for title
QFont titleFont("Segoe UI", 32, QFont::Bold);
painter->setFont(titleFont);
QString gwf = "GWF";
QString rest = " PowerUp Injector";
QFontMetrics fm(titleFont);
int gwfWidth = fm.horizontalAdvance(gwf);
int restWidth = fm.horizontalAdvance(rest);
int totalWidth = gwfWidth + restWidth;
int titleX = (WIDTH - totalWidth) / 2;
int titleY = 100;
// Draw shadow
painter->setPen(QColor(0, 0, 0, 80));
painter->drawText(titleX + 2, titleY + 2, gwf);
painter->drawText(titleX + gwfWidth + 2, titleY + 2, rest);
// Draw "GWF" in gold/yellow accent
painter->setPen(mGold);
painter->drawText(titleX, titleY, gwf);
// Draw rest in white
painter->setPen(mWhite);
painter->drawText(titleX + gwfWidth, titleY, rest);
// Tagline
QFont taglineFont("Segoe UI", 11);
painter->setFont(taglineFont);
painter->setPen(mLightGray);
QString tagline = "Add powerups to your workshop maps!";
QFontMetrics taglineFm(taglineFont);
int taglineWidth = taglineFm.horizontalAdvance(tagline);
painter->drawText((WIDTH - taglineWidth) / 2, titleY + 28, tagline);
// Version info
QFont versionFont("Segoe UI", 9);
painter->setFont(versionFont);
painter->setPen(QColor(255, 255, 255, 150));
QString version = QString("Version %1").arg(QCoreApplication::applicationVersion());
QFontMetrics versionFm(versionFont);
int versionWidth = versionFm.horizontalAdvance(version);
painter->drawText((WIDTH - versionWidth) / 2, titleY + 48, version);
// Copyright
QString copyright = QString("%1 %2").arg(QCoreApplication::organizationName())
.arg(QDate::currentDate().year());
int copyrightWidth = versionFm.horizontalAdvance(copyright);
painter->drawText((WIDTH - copyrightWidth) / 2, HEIGHT - 15, copyright);
}
void SplashScreen::drawProgressBar(QPainter *painter)
{
int progressX = 40;
int progressY = HEIGHT - 60;
int progressWidth = WIDTH - 80;
int progressHeight = 12;
// Progress bar background
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(0, 0, 0, 80));
painter->drawRoundedRect(progressX, progressY, progressWidth, progressHeight, 6, 6);
// Progress bar fill
if (mProgressMax > 0 && mProgress > 0) {
int fillWidth = (progressWidth * mProgress) / mProgressMax;
if (fillWidth > 0) {
// Gradient fill for progress
QLinearGradient gradient(progressX, progressY, progressX + fillWidth, progressY);
gradient.setColorAt(0, mGold);
gradient.setColorAt(0.5, mGold.lighter(110));
gradient.setColorAt(1, mGold);
painter->setBrush(gradient);
painter->drawRoundedRect(progressX, progressY, fillWidth, progressHeight, 6, 6);
// Add shine effect
painter->setBrush(QColor(255, 255, 255, 60));
painter->drawRoundedRect(progressX, progressY, fillWidth, progressHeight / 2, 6, 6);
}
}
}
void SplashScreen::drawStatusText(QPainter *painter)
{
if (!mStatus.isEmpty()) {
QFont statusFont("Segoe UI", 9);
painter->setFont(statusFont);
painter->setPen(mWhite);
int progressX = 40;
int progressY = HEIGHT - 60;
int progressWidth = WIDTH - 80;
QFontMetrics statusFm(statusFont);
QString elidedStatus = statusFm.elidedText(mStatus, Qt::ElideMiddle, progressWidth);
painter->drawText(progressX, progressY - 8, elidedStatus);
}
}

58
splashscreen.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QTimer>
#include <QColor>
#include <QMouseEvent>
#include <QKeyEvent>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QWidget *parent = nullptr);
~SplashScreen();
void setStatus(const QString &message);
void setProgress(int value, int max = 100);
void startAnimation();
void stopAnimation();
void finish(QWidget *mainWindow);
protected:
void drawContents(QPainter *painter) override;
void mousePressEvent(QMouseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
private slots:
void onAnimationTick();
private:
void drawBackground(QPainter *painter);
void drawGolfBall(QPainter *painter, int x, int y, int radius);
void drawPowerupIcons(QPainter *painter);
void drawTitle(QPainter *painter);
void drawProgressBar(QPainter *painter);
void drawStatusText(QPainter *painter);
QString mStatus;
int mProgress = 0;
int mProgressMax = 100;
QTimer *mAnimationTimer;
float mAnimationPhase = 0.0f; // For bouncing golf ball animation
// Theme colors - golf course green theme
QColor mGreenLight = QColor("#4CAF50"); // Light green
QColor mGreenDark = QColor("#2E7D32"); // Dark green
QColor mGold = QColor("#FFD700"); // Golden yellow for progress
QColor mWhite = QColor("#FFFFFF"); // White text
QColor mLightGray = QColor("#E0E0E0"); // Light gray for muted text
static constexpr int WIDTH = 480;
static constexpr int HEIGHT = 300;
};
#endif // SPLASHSCREEN_H