#include "mainwindow.h" #include "ui_mainwindow.h" #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , 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()) { emit loadingProgress(100, "Ready (no maps found)"); emit loadingComplete(); return; } qDebug() << "Found Golf With Friends Workshop Path:" << gwfWorkshopDir.path(); QStringList entries = gwfWorkshopDir.entryList({}, QDir::NoDotAndDotDot | QDir::Dirs, QDir::Time); int totalEntries = entries.size(); int processed = 0; foreach (const QString entry, entries) { // 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(); 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); processed++; } emit loadingProgress(95, "Updating level list..."); QCoreApplication::processEvents(); updateLevels(); emit loadingProgress(100, "Ready!"); emit loadingComplete(); } void MainWindow::findSteamPath() { // 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) { QDir testDir(path); if (testDir.exists()) { mSteamPath = testDir.path(); break; } } // If not found in common locations, ask user if (mSteamPath.isEmpty()) { mSteamPath = QFileDialog::getExistingDirectory(this, "Select Steam Directory", "C:/Program Files (x86)/", QFileDialog::ShowDirsOnly); } if (!mSteamPath.isEmpty()) { // 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() { 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) { // 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); 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); // 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)); } } void MainWindow::on_listWidget_powerups_currentRowChanged(int 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()); 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)) { 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) { 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)) { QMessageBox::critical(this, "Save Error", QString("Failed to open backup file for reading:\n%1").arg(backupMapPath)); return; } const QByteArray backupData = backupMapFile.readAll().simplified(); backupMapFile.close(); QFile mapFile(mapPath); if (!mapFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { 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++) { 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) .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)); 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 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) { 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.mRotation = QVector4D(0, 0, 0, 1); // Identity quaternion newPowerUp.mScale = QVector3D(1, 1, 1); selectedLevel.mPowerUps.push_back(newPowerUp); generatedCount++; } } saveMap(selectedLevel, true); mLevels[currentListRow] = selectedLevel; // 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); } }