diff --git a/GWF_PowerupInjector.pro b/GWF_PowerupInjector.pro new file mode 100644 index 0000000..3dc3aab --- /dev/null +++ b/GWF_PowerupInjector.pro @@ -0,0 +1,22 @@ +QT += core gui widgets + +CONFIG += c++17 + +# 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 + +HEADERS += \ + mainwindow.h + +FORMS += \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..ab6f5c2 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,308 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , mSteamPath() +{ + ui->setupUi(this); + + findSteamPath(); + + QDir gwfWorkshopDir(mSteamPath + "/steamapps/workshop/content/431240"); + if (!gwfWorkshopDir.exists()) { return; } + + qDebug() << "Found Golf With Friends Workshop Path:" << gwfWorkshopDir.path(); + + foreach (const QString entry, gwfWorkshopDir.entryList({}, QDir::NoFilter, QDir::Time)) + { + if (entry == "." || entry == "..") { continue; } + + QFile newEntry(gwfWorkshopDir.path() + "/" + entry + "/Map"); + if (!newEntry.open(QIODevice::ReadWrite)) + { + qDebug() << "Failed to open workshop map:" << entry; + continue; + } + const QByteArray rawMapData = newEntry.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(); + + LevelInfo newInfo; + newInfo.mLevelName = loadObj["levelName"].toString() + QString(" [%1]").arg(entry); + newInfo.mDescription = loadObj["description"].toString(); + newInfo.mPublishedID = QString::number(loadObj["publishedID"].toInt()); + newInfo.mMusic = loadObj["music"].toInt(); + newInfo.mSkybox = loadObj["skybox"].toInt(); + newInfo.mPreview = QPixmap(gwfWorkshopDir.path() + "/" + entry + "/Preview.jpg"); + newInfo.mPowerUps = findPowerUps(loadObj); + newInfo.mMaxViewID = getMaxViewID(loadObj); + newInfo.mObjectPositions = getObjectPositions(loadObj); + newInfo.mMapPath = newEntry.fileName(); + newInfo.mMapData = loadObj; + + mLevels.push_back(newInfo); + } + + updateLevels(); +} + +void MainWindow::findSteamPath() +{ + QDir testDir("C:/Program Files (x86)/Steam"); + if (testDir.exists()) + { + mSteamPath = testDir.path(); + } + else + { + 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)); + ui->lineEdit_SteamPath->setEnabled(false); + } +} + +void MainWindow::updateLevels() +{ + ui->listWidget_MapSelect->clear(); + for (int i = 0; i < mLevels.size(); i++) + { + ui->listWidget_MapSelect->addItem(mLevels[i].mLevelName); + } +} + +QVector MainWindow::getObjectPositions(QJsonObject aMapData) +{ + QVector result; + QJsonArray objData = aMapData["editorObjectData"].toArray(); + + for (int i = 0; i < objData.size(); i++) + { + QJsonObject currentObject = objData[i].toObject(); + + if (currentObject["obName"].toString().contains("base", Qt::CaseInsensitive)) + { + QVector3D position; + position.setX(currentObject["pX"].toDouble()); + position.setY(currentObject["pY"].toDouble()); + position.setZ(currentObject["pZ"].toDouble()); + result.push_back(position); + } + } + return result; +} + +int MainWindow::getMaxViewID(QJsonObject aMapData) +{ + QVector result; + QJsonArray objData = aMapData["editorObjectData"].toArray(); + int maxViewID = 0; + + for (int i = 0; i < objData.size(); i++) + { + QJsonObject currentObject = objData[i].toObject(); + + QJsonArray viewIDs = currentObject["photonData"].toObject()["photonViewID"].toArray(); + + if (viewIDs.isEmpty()) { continue; } + + maxViewID = fmax(viewIDs.first().toInt(), maxViewID); + } + return maxViewID; +} + +QVector MainWindow::findPowerUps(QJsonObject aMapData) +{ + QVector result; + QJsonArray objData = aMapData["editorObjectData"].toArray(); + + for (int i = 0; i < objData.size(); i++) + { + QJsonObject currentObject = objData[i].toObject(); + const QString objName = currentObject["obName"].toString(); + if (objName == "Powerup Spawner") + { + PowerUpInfo newPowerUp; + newPowerUp.mPosition.setX(currentObject["pX"].toDouble()); + newPowerUp.mPosition.setY(currentObject["pY"].toDouble()); + newPowerUp.mPosition.setZ(currentObject["pZ"].toDouble()); + + newPowerUp.mRotation.setW(currentObject["rW"].toDouble()); + newPowerUp.mRotation.setX(currentObject["rX"].toDouble()); + newPowerUp.mRotation.setY(currentObject["rY"].toDouble()); + newPowerUp.mRotation.setZ(currentObject["rZ"].toDouble()); + + newPowerUp.mScale.setX(currentObject["sX"].toDouble()); + newPowerUp.mScale.setY(currentObject["sY"].toDouble()); + newPowerUp.mScale.setZ(currentObject["sZ"].toDouble()); + result.push_back(newPowerUp); + } + } + return result; +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::on_listWidget_MapSelect_currentRowChanged(int currentRow) +{ + LevelInfo selectedLevel = mLevels[currentRow]; + ui->lineEdit_LevelName->setText(selectedLevel.mLevelName); + ui->lineEdit_Description->setText(selectedLevel.mDescription); + ui->lineEdit_ID->setText(selectedLevel.mPublishedID); + ui->spinBox_Music->setValue(selectedLevel.mMusic); + ui->spinBox_Skybox->setValue(selectedLevel.mSkybox); + ui->spinBox_PowerupCount->setValue(selectedLevel.mPowerUps.size()); + + QPixmap scaledPreview = selectedLevel.mPreview.scaled(ui->label_MapPreview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + ui->label_MapPreview->setPixmap(scaledPreview); + + for (int i = 0; i < selectedLevel.mPowerUps.size(); i++) + { + ui->listWidget_powerups->addItem(QString("Powerup %1").arg(i)); + } +} + + +void MainWindow::on_listWidget_powerups_currentRowChanged(int currentRow) +{ + LevelInfo selectedLevel = mLevels[ui->listWidget_MapSelect->currentRow()]; + PowerUpInfo selectedPowerUp = selectedLevel.mPowerUps[currentRow]; + + ui->doubleSpinBox_PosX->setValue(selectedPowerUp.mPosition.x()); + ui->doubleSpinBox_PosY->setValue(selectedPowerUp.mPosition.y()); + ui->doubleSpinBox_PosZ->setValue(selectedPowerUp.mPosition.z()); + + ui->doubleSpinBox_RotW->setValue(selectedPowerUp.mRotation.w()); + ui->doubleSpinBox_RotX->setValue(selectedPowerUp.mRotation.x()); + ui->doubleSpinBox_RotY->setValue(selectedPowerUp.mRotation.y()); + ui->doubleSpinBox_RotZ->setValue(selectedPowerUp.mRotation.z()); + + ui->doubleSpinBox_ScaleX->setValue(selectedPowerUp.mScale.x()); + ui->doubleSpinBox_ScaleY->setValue(selectedPowerUp.mScale.y()); + ui->doubleSpinBox_ScaleZ->setValue(selectedPowerUp.mScale.z()); +} + +void MainWindow::saveMap(LevelInfo aLevelInfo, bool aBackup) +{ + const QString mapPath = aLevelInfo.mMapPath; + if (!QFile::exists(mapPath)) + { + qDebug() << "File doesn't exist: " << mapPath; + return; + } + + const QString backupMapPath = aLevelInfo.mMapPath + ".old"; + if (!QFile::exists(backupMapPath) && aBackup) + { + QFile::copy(mapPath, backupMapPath); + } + + QFile backupMapFile(backupMapPath); + if (!backupMapFile.open(QIODevice::ReadOnly)) + { + qDebug() << "Failed to open backup file: " << mapPath; + return; + } + + const QByteArray backupData = backupMapFile.readAll().simplified(); + backupMapFile.close(); + + QFile mapFile(mapPath); + if (!mapFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) + { + qDebug() << "Failed to open file: " << mapPath; + return; + } + + + QByteArray powerUpsData = ","; + + for (int i = 0; i < aLevelInfo.mPowerUps.size(); i++) + { + 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) + .arg(powerUp.mPosition.y(), 0, 'f', 1) + .arg(powerUp.mPosition.z(), 0, 'f', 1) + .arg(powerUp.mRotation.w(), 0, 'f', 1) + .arg(powerUp.mRotation.x(), 0, 'f', 1) + .arg(powerUp.mRotation.y(), 0, 'f', 1) + .arg(powerUp.mRotation.z(), 0, 'f', 1) + .arg(powerUp.mScale.x(), 0, 'f', 1) + .arg(powerUp.mScale.y(), 0, 'f', 1) + .arg(powerUp.mScale.z(), 0, 'f', 1) + .arg(i + 4 + aLevelInfo.mMaxViewID); + powerUpsData.append(powerUpData.toUtf8()); + } + powerUpsData = powerUpsData.mid(0, powerUpsData.size() - 1); + + int injectionPoint = backupData.lastIndexOf(']'); + + QByteArray mapData; + mapData.append(backupData.mid(0, injectionPoint)); + mapData.append(powerUpsData); + mapData.append(backupData.mid(injectionPoint)); + + mapFile.write(mapData); + mapFile.close(); +} + +void MainWindow::on_pushButton_Generate_clicked() +{ + int currentListRow = ui->listWidget_MapSelect->currentRow(); + LevelInfo selectedLevel = mLevels[currentListRow]; + + QVector shuffledPositions = selectedLevel.mObjectPositions; + auto rng = QRandomGenerator::global(); + std::shuffle(shuffledPositions.begin(), shuffledPositions.end(), *rng); + + for (int i = 0; i < shuffledPositions.size(); i++) + { + if (i == MAX_POWERUPS) + { + break; + } + if ((rand() % 100) < 75) + { + QVector3D pos = shuffledPositions[i]; + + int sign = 1; + if ((rand() % 100) < 50) + { + sign = -1; + } + + pos.setX(pos.x() + sign * (rand() % 2)); + pos.setY(pos.y() + sign * (rand() % 2)); + + PowerUpInfo newPowerUp; + newPowerUp.mPosition = QVector3D(pos.x(), pos.y() + 0.25, pos.z()); + newPowerUp.mScale = QVector3D(1, 1, 1); + selectedLevel.mPowerUps.push_back(newPowerUp); + } + } + saveMap(selectedLevel, true); + + mLevels[ui->listWidget_MapSelect->currentRow()] = selectedLevel; + + ui->listWidget_MapSelect->setCurrentRow(currentListRow); +} + diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..fec65a7 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,78 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +struct PowerUpInfo +{ + QVector3D mPosition; + QVector4D mRotation; + QVector3D mScale; +}; + +struct LevelInfo +{ + QString mLevelName; + QString mDescription; + QString mPublishedID; + int mMusic; + int mSkybox; + QPixmap mPreview; + QVector mPowerUps; + int mMaxViewID; + QVector mObjectPositions; + + QString mMapPath; + QJsonObject mMapData; +}; + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +#define MAX_POWERUPS 100 + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + void findSteamPath(); + void updateLevels(); + QVector findPowerUps(QJsonObject aMapData); + + QVector getObjectPositions(QJsonObject aObj); + void saveMap(LevelInfo aLevelInfo, bool aBackup = true); + QByteArray serializePowerUpCompact(const PowerUpInfo &p, int photonId); + int getMaxViewID(QJsonObject aMapData); +private slots: + void on_listWidget_MapSelect_currentRowChanged(int currentRow); + + void on_listWidget_powerups_currentRowChanged(int currentRow); + + void on_pushButton_Generate_clicked(); + +private: + Ui::MainWindow *ui; + + QString mSteamPath; + QVector mLevels; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..a66b0bf --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,605 @@ + + + MainWindow + + + + 0 + 0 + 1330 + 585 + + + + + 1231 + 580 + + + + MainWindow + + + + + + + + + Steam Path: + + + + + + + NOT FOUND + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + Map Details + + + + + + + + + + Level Name: + + + + + + + + 0 + 0 + + + + Select a level + + + + + + + + + + + Description: + + + + + + + + 0 + 0 + + + + Select a level + + + + + + + + + + + Published ID: + + + + + + + + 0 + 0 + + + + Select a level + + + + + + + + + + 0 + 0 + + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + + + + + Music: + + + + + + + + + + + + + + + + + Skybox: + + + + + + + + + + + + + + + + + Powerup Count: + + + + + + + false + + + + + + + + + + 0 + 0 + + + + Powerup Details + + + + + + + 0 + 0 + + + + + 125 + 16777215 + + + + + + + + + + + + X + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + Y + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + Z + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + Z + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + X + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + Y + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + Z + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + Position + + + + + + + Rotation + + + + + + + Scale + + + + + + + Y + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + W + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + X + + + + + + + false + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + false + + + Save Powerup + + + + + + + false + + + New Powerup + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Ignored + + + + 40 + 20 + + + + + + + + Generate + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Expanding + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + 0 + 0 + 1330 + 21 + + + + + + + +