From 8e02a0ecda3149916ff5976cdfbef599df1f2fe7 Mon Sep 17 00:00:00 2001 From: Rosalie Wanders Date: Wed, 29 Jan 2025 18:30:47 +0100 Subject: [PATCH] RMG: implement netplay cheat support --- Source/RMG/CMakeLists.txt | 1 + .../Dialog/Cheats/CheatsCommon.cpp | 294 ++++++++++++++++++ .../Dialog/Cheats/CheatsCommon.hpp | 39 +++ .../Dialog/Cheats/CheatsDialog.cpp | 35 ++- .../Dialog/Cheats/CheatsDialog.hpp | 9 +- .../Dialog/Cheats/ChooseCheatOptionDialog.cpp | 31 +- .../Dialog/Cheats/ChooseCheatOptionDialog.hpp | 11 +- .../Dialog/Netplay/NetplaySessionDialog.cpp | 118 ++++++- .../Dialog/Netplay/NetplaySessionDialog.hpp | 6 +- .../Dialog/Netplay/NetplaySessionDialog.ui | 2 +- 10 files changed, 512 insertions(+), 34 deletions(-) create mode 100644 Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.cpp create mode 100644 Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.hpp diff --git a/Source/RMG/CMakeLists.txt b/Source/RMG/CMakeLists.txt index 886ffd02..0196d052 100644 --- a/Source/RMG/CMakeLists.txt +++ b/Source/RMG/CMakeLists.txt @@ -51,6 +51,7 @@ set(RMG_SOURCES UserInterface/Widget/KeybindButton.cpp UserInterface/Dialog/SettingsDialog.cpp UserInterface/Dialog/SettingsDialog.ui + UserInterface/Dialog/Cheats/CheatsCommon.cpp UserInterface/Dialog/Cheats/CheatsDialog.cpp UserInterface/Dialog/Cheats/CheatsDialog.ui UserInterface/Dialog/Cheats/AddCheatDialog.cpp diff --git a/Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.cpp b/Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.cpp new file mode 100644 index 00000000..35621e0d --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.cpp @@ -0,0 +1,294 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "CheatsCommon.hpp" + +#include + +#include + +using namespace CheatsCommon; + +// +// Local Functions +// + +static int find_json_cheat(const QJsonArray& json, const CoreCheat& cheat) +{ + QJsonObject cheatObject; + QString targetCheatName = QString::fromStdString(cheat.Name); + + for (qsizetype i = 0; i < json.size(); i++) + { + cheatObject = json[i].toObject(); + + if (cheatObject["name"].toString() == targetCheatName) + { + return (int)i; + } + } + + return -1; +} + +// +// Exported Function +// + +bool CheatsCommon::IsCheatEnabled(bool netplay, const QJsonArray& json, const CoreCheat& cheat) +{ + if (netplay) + { + int index = find_json_cheat(json, cheat); + return index != -1 && json[index].toObject().contains("codes"); + } + else + { + return CoreIsCheatEnabled(cheat); + } +} + +bool CheatsCommon::EnableCheat(bool netplay, QJsonArray& json, const CoreCheat& cheat, bool enabled) +{ + if (netplay) + { + QJsonObject cheatObject; + QJsonArray codesArray; + QString targetCheatName = QString::fromStdString(cheat.Name); + + if (enabled) + { + for (const auto& cheatCode : cheat.CheatCodes) + { + QJsonObject codeObject; + codeObject["address"] = (qint64)cheatCode.Address; + codeObject["value"] = (qint64)cheatCode.Value; + codeObject["use_option"] = cheatCode.UseOptions; + codeObject["option_index"] = cheatCode.OptionIndex; + codeObject["option_size"] = cheatCode.OptionSize; + codesArray.push_back(codeObject); + } + + cheatObject["name"] = targetCheatName; + cheatObject["codes"] = codesArray; + cheatObject["has_options"] = cheat.HasOptions; + + int index = find_json_cheat(json, cheat); + if (index != -1) + { + // copy over the option object + cheatObject["option"] = json[index].toObject().value("option").toObject(); + json[index] = cheatObject; + } + else + { + json.push_back(cheatObject); + } + } + else + { + int index = find_json_cheat(json, cheat); + if (index != -1) + { + cheatObject = json[index].toObject(); + + // if cheat object has an option key + // we have to keep that key because + // else the UI will behave inconsistent + // with the non-netplay cheats dialog + if (cheatObject.contains("option")) + { + cheatObject.remove("codes"); + json[index] = cheatObject; + } + else + { + json.removeAt(index); + } + } + } + + return true; + } + else + { + return CoreEnableCheat(cheat, enabled); + } +} + +bool CheatsCommon::HasCheatOptionSet(bool netplay, const QJsonArray& json, const CoreCheat& cheat) +{ + if (netplay) + { + int index = find_json_cheat(json, cheat); + return index != -1 && json[index].toObject().contains("option"); + } + else + { + return CoreHasCheatOptionSet(cheat); + } +} + +bool CheatsCommon::GetCheatOption(bool netplay, const QJsonArray& json, const CoreCheat& cheat, CoreCheatOption& option) +{ + if (netplay) + { + QJsonObject cheatObject; + QJsonObject optionObject; + + int index = find_json_cheat(json, cheat); + if (index == -1) + { + return false; + } + + cheatObject = json[index].toObject(); + optionObject = cheatObject["option"].toObject(); + option.Name = optionObject["name"].toString().toStdString(); + option.Size = optionObject["size"].toInt(); + option.Value = optionObject["value"].toInteger(); + return true; + } + else + { + return CoreGetCheatOption(cheat, option); + } +} +bool CheatsCommon::SetCheatOption(bool netplay, QJsonArray& json, const CoreCheat& cheat, CoreCheatOption& option) +{ + if (netplay) + { + QJsonObject cheatObject; + QJsonObject optionObject; + QString targetCheatName = QString::fromStdString(cheat.Name); + + int index = find_json_cheat(json, cheat); + if (index != -1) + { + cheatObject = json[index].toObject(); + optionObject["name"] = QString::fromStdString(option.Name); + optionObject["size"] = option.Size; + optionObject["value"] = (qint64)option.Value; + + cheatObject["option"] = optionObject; + + json[index] = cheatObject; + } + else + { // create dummy object when option has been set + // without the user having the cheat enabled + optionObject["name"] = QString::fromStdString(option.Name); + optionObject["size"] = option.Size; + optionObject["value"] = (qint64)option.Value; + + cheatObject["name"] = targetCheatName; + cheatObject["option"] = optionObject; + + json.append(cheatObject); + } + + return true; + } + else + { + return CoreGetCheatOption(cheat, option); + } +} + +bool CheatsCommon::ParseCheatJson(const QJsonArray& json, std::vector& cheats) +{ + QJsonObject cheatObject; + QJsonArray cheatCodesArray; + QJsonObject cheatCodeObject; + QJsonObject cheatOptionObject; + CoreCheat cheat; + CoreCheatCode cheatCode; + CoreCheatOption cheatOption; + bool skipCheat = false; + + for (qsizetype i = 0; i < json.size(); i++) + { + cheatObject = json[i].toObject(); + + if (!cheatObject.contains("name") || + !cheatObject.contains("codes") || + !cheatObject.contains("has_options")) + { // skip disabled cheats + continue; + } + + // reset cheat + cheat = {}; + skipCheat = false; + + cheat.Name = cheatObject["name"].toString().toStdString(); + cheat.HasOptions = cheatObject["has_options"].toBool(); + + if (cheat.Name.empty()) + { // name validation + continue; + } + + // parse cheat codes + cheatCodesArray = cheatObject["codes"].toArray(); + for (qsizetype x = 0; x < cheatCodesArray.size(); x++) + { + cheatCodeObject = cheatCodesArray[x].toObject(); + + // ensure json object contains required keys + if (!cheatCodeObject.contains("address") || + !cheatCodeObject.contains("value") || + !cheatCodeObject.contains("use_option") || + !cheatCodeObject.contains("option_index") || + !cheatCodeObject.contains("option_size")) + { + skipCheat = true; + break; + } + + cheatCode.Address = (uint32_t)cheatCodeObject["address"].toInteger(); + cheatCode.Value = cheatCodeObject["value"].toInt(); + cheatCode.UseOptions = cheatCodeObject["use_option"].toBool(); + cheatCode.OptionIndex = cheatCodeObject["option_index"].toInt(); + cheatCode.OptionSize = cheatCodeObject["option_size"].toInt(); + + cheat.CheatCodes.push_back(cheatCode); + } + + // parse (optional) cheat option + if (cheatObject.contains("option")) + { + cheatOptionObject = cheatObject["option"].toObject(); + + // ensure json object has required keys + if (!cheatOptionObject.contains("name") || + !cheatOptionObject.contains("size") || + !cheatOptionObject.contains("value")) + { + skipCheat = true; + } + else + { + cheatOption.Name = cheatOptionObject["name"].toString().toStdString(); + cheatOption.Size = cheatOptionObject["size"].toInt(); + cheatOption.Value = (uint32_t)cheatOptionObject["value"].toInteger(); + cheat.CheatOptions.push_back(cheatOption); + } + } + + if (skipCheat) + { + continue; + } + + cheats.push_back(cheat); + } + + return true; +} diff --git a/Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.hpp b/Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.hpp new file mode 100644 index 00000000..c4e3b6f3 --- /dev/null +++ b/Source/RMG/UserInterface/Dialog/Cheats/CheatsCommon.hpp @@ -0,0 +1,39 @@ +/* + * Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG + * Copyright (C) 2020 Rosalie Wanders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CHEATSCOMMONH_HPP +#define CHEATSCOMMONH_HPP + +#include +#include + +#include + +namespace CheatsCommon +{ + // Checks whether cheat is enabled + bool IsCheatEnabled(bool netplay, const QJsonArray& json, const CoreCheat& cheat); + + // Enables Cheat + bool EnableCheat(bool netplay, QJsonArray& json, const CoreCheat& cheat, bool enabled); + + // Returns whether cheat has option set + bool HasCheatOptionSet(bool netplay, const QJsonArray& json, const CoreCheat& cheat); + + // Retrieves cheat option + bool GetCheatOption(bool netplay, const QJsonArray& json, const CoreCheat& cheat, CoreCheatOption& option); + + // Sets cheat option + bool SetCheatOption(bool netplay, QJsonArray& json, const CoreCheat& cheat, CoreCheatOption& option); + + // Parses json into cheat list + bool ParseCheatJson(const QJsonArray& json, std::vector& cheats); +} + +#endif // CHEATSCOMMONH_HPP diff --git a/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.cpp b/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.cpp index b9775073..2f73e5c7 100644 --- a/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.cpp +++ b/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.cpp @@ -11,9 +11,10 @@ #include "AddCheatDialog.hpp" #include "ChooseCheatOptionDialog.hpp" - #include "Utilities/QtMessageBox.hpp" +#include "CheatsCommon.hpp" +#include #include #include @@ -23,10 +24,13 @@ Q_DECLARE_METATYPE(CoreCheat); using namespace UserInterface::Dialog; using namespace Utilities; -CheatsDialog::CheatsDialog(QWidget *parent) : QDialog(parent) +CheatsDialog::CheatsDialog(QWidget *parent, bool netplay, QJsonArray cheatsJson) : QDialog(parent) { qRegisterMetaType(); + this->netplay = netplay; + this->cheatsJson = cheatsJson; + this->setupUi(this); this->loadCheats(); } @@ -40,6 +44,11 @@ bool CheatsDialog::HasFailed(void) return this->failedToParseCheats; } +QJsonArray CheatsDialog::GetJson(void) +{ + return this->cheatsJson; +} + void CheatsDialog::loadCheats(void) { std::vector cheats; @@ -59,7 +68,7 @@ void CheatsDialog::loadCheats(void) QString name = QString::fromStdString(cheat.Name); QString section; QStringList sections = name.split("\\"); - bool enabled = CoreIsCheatEnabled(cheat); + bool enabled = CheatsCommon::IsCheatEnabled(this->netplay, this->cheatsJson, cheat); for (int i = 0; i < sections.size(); i++) { @@ -173,7 +182,8 @@ QString CheatsDialog::getTreeWidgetItemTextFromCheat(CoreCheat cheat) if (cheat.HasOptions) { CoreCheatOption cheatOption; - if (!CoreHasCheatOptionSet(cheat) || !CoreGetCheatOption(cheat, cheatOption)) + if (!CheatsCommon::HasCheatOptionSet(this->netplay, this->cheatsJson, cheat) || + !CheatsCommon::GetCheatOption(this->netplay, this->cheatsJson, cheat, cheatOption)) { text = cheatName + " (=> ???? - Not Set)"; } @@ -203,7 +213,7 @@ void CheatsDialog::on_cheatsTreeWidget_itemChanged(QTreeWidgetItem *item, int co CoreCheat cheat = item->data(column, Qt::UserRole).value(); bool enabled = (item->checkState(column) == Qt::CheckState::Checked); - if (enabled && cheat.HasOptions && !CoreHasCheatOptionSet(cheat)) + if (enabled && cheat.HasOptions && !CheatsCommon::HasCheatOptionSet(this->netplay, this->cheatsJson, cheat)) { // ask user to select an option this->on_cheatsTreeWidget_itemDoubleClicked(item, column); @@ -211,14 +221,14 @@ void CheatsDialog::on_cheatsTreeWidget_itemChanged(QTreeWidgetItem *item, int co // make sure the user selected an option, // if they did then enable the cheat as requested, // if not, reset the checkbox - if (!CoreHasCheatOptionSet(cheat)) + if (!CheatsCommon::HasCheatOptionSet(this->netplay, this->cheatsJson, cheat)) { item->setCheckState(0, Qt::CheckState::Unchecked); return; } } - CoreEnableCheat(cheat, enabled); + CheatsCommon::EnableCheat(this->netplay, this->cheatsJson, cheat, enabled); } void CheatsDialog::on_cheatsTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) @@ -251,9 +261,12 @@ void CheatsDialog::on_cheatsTreeWidget_itemDoubleClicked(QTreeWidgetItem *item, return; } - Dialog::ChooseCheatOptionDialog dialog(cheat, this); + Dialog::ChooseCheatOptionDialog dialog(cheat, this->netplay, this->cheatsJson, this); dialog.exec(); + // update json + this->cheatsJson = dialog.GetJson(); + // re-load cheat options item->setText(column, this->getTreeWidgetItemTextFromCheat(cheat)); } @@ -312,6 +325,12 @@ void CheatsDialog::on_removeCheatButton_clicked(void) void CheatsDialog::accept(void) { + if (this->netplay) + { + QDialog::accept(); + return; + } + CoreSettingsSave(); if (!CoreApplyCheats()) diff --git a/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.hpp b/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.hpp index f2e68040..91509ea1 100644 --- a/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.hpp +++ b/Source/RMG/UserInterface/Dialog/Cheats/CheatsDialog.hpp @@ -10,8 +10,9 @@ #ifndef CHEATSDIALOG_HPP #define CHEATSDIALOG_HPP -#include +#include #include +#include #include @@ -26,14 +27,18 @@ class CheatsDialog : public QDialog, private Ui::CheatsDialog Q_OBJECT public: - CheatsDialog(QWidget *parent); + CheatsDialog(QWidget *parent, bool netplay = false, QJsonArray cheatsJson = {}); ~CheatsDialog(void); bool HasFailed(void); + QJsonArray GetJson(void); + private: bool needCloseRom = false; bool failedToParseCheats = false; + bool netplay = false; + QJsonArray cheatsJson; void loadCheats(void); diff --git a/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.cpp b/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.cpp index a267fe59..50f36835 100644 --- a/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.cpp +++ b/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.cpp @@ -8,6 +8,7 @@ * along with this program. If not, see . */ #include "ChooseCheatOptionDialog.hpp" +#include "CheatsCommon.hpp" #include @@ -17,17 +18,19 @@ Q_DECLARE_METATYPE(CoreCheatOption); using namespace UserInterface::Dialog; -ChooseCheatOptionDialog::ChooseCheatOptionDialog(CoreCheat cheat, QWidget *parent) : QDialog(parent) +ChooseCheatOptionDialog::ChooseCheatOptionDialog(CoreCheat cheat, bool netplay, QJsonArray cheatsJson, QWidget *parent) : QDialog(parent) { qRegisterMetaType(); this->setupUi(this); this->cheat = cheat; - + this->netplay = netplay; + this->cheatsJson = cheatsJson; + CoreCheatOption setCheatOption; - bool checkCheatOption = CoreHasCheatOptionSet(cheat) && - CoreGetCheatOption(cheat, setCheatOption); + bool checkCheatOption = CheatsCommon::HasCheatOptionSet(this->netplay, this->cheatsJson, cheat) && + CheatsCommon::GetCheatOption(this->netplay, this->cheatsJson, cheat, setCheatOption); for (CoreCheatOption& option : cheat.CheatOptions) { @@ -49,7 +52,11 @@ ChooseCheatOptionDialog::ChooseCheatOptionDialog(CoreCheat cheat, QWidget *paren ChooseCheatOptionDialog::~ChooseCheatOptionDialog() { +} +QJsonArray ChooseCheatOptionDialog::GetJson(void) +{ + return this->cheatsJson; } void ChooseCheatOptionDialog::on_cheatOptionsTreeWidget_itemChanged(QTreeWidgetItem *item, int column) @@ -70,17 +77,8 @@ void ChooseCheatOptionDialog::on_cheatOptionsTreeWidget_itemChanged(QTreeWidgetI } } -void ChooseCheatOptionDialog::on_buttonBox_clicked(QAbstractButton *button) +void ChooseCheatOptionDialog::accept(void) { - QPushButton *pushButton = (QPushButton *)button; - QPushButton *okButton = this->buttonBox->button(QDialogButtonBox::Ok); - - // do nothing when the user didn't press ok - if (pushButton != okButton) - { - return; - } - for (int i = 0; i < this->cheatOptionsTreeWidget->topLevelItemCount(); i++) { QTreeWidgetItem* item = this->cheatOptionsTreeWidget->topLevelItem(i); @@ -88,8 +86,9 @@ void ChooseCheatOptionDialog::on_buttonBox_clicked(QAbstractButton *button) if (item->checkState(0) == Qt::CheckState::Checked) { CoreCheatOption cheatOption = item->data(0, Qt::UserRole).value(); - CoreSetCheatOption(this->cheat, cheatOption); - return; + CheatsCommon::SetCheatOption(this->netplay, this->cheatsJson, this->cheat, cheatOption); } } + + QDialog::accept(); } diff --git a/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.hpp b/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.hpp index 7420792b..caa7b9e6 100644 --- a/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.hpp +++ b/Source/RMG/UserInterface/Dialog/Cheats/ChooseCheatOptionDialog.hpp @@ -10,8 +10,9 @@ #ifndef CHOOSECHEATOPTIONDIALOG_HPP #define CHOOSECHEATOPTIONDIALOG_HPP -#include +#include #include +#include #include @@ -27,14 +28,18 @@ class ChooseCheatOptionDialog : public QDialog, private Ui::ChooseCheatOptionDia private: CoreCheat cheat; + bool netplay; + QJsonArray cheatsJson; public: - ChooseCheatOptionDialog(CoreCheat cheat, QWidget *parent); + ChooseCheatOptionDialog(CoreCheat cheat, bool netplay, QJsonArray cheatsJson, QWidget *parent); ~ChooseCheatOptionDialog(void); + QJsonArray GetJson(void); + private slots: void on_cheatOptionsTreeWidget_itemChanged(QTreeWidgetItem *item, int column); - void on_buttonBox_clicked(QAbstractButton *button); + void accept(void) Q_DECL_OVERRIDE; }; } // namespace Dialog } // namespace UserInterface diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp index b78bf530..2c8c316e 100644 --- a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.cpp @@ -7,9 +7,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "UserInterface/Dialog/Cheats/CheatsCommon.hpp" +#include "UserInterface/Dialog/Cheats/CheatsDialog.hpp" +#include "Utilities/QtMessageBox.hpp" #include "NetplaySessionDialog.hpp" #include "NetplayCommon.hpp" -#include "Utilities/QtMessageBox.hpp" #include #include @@ -31,6 +33,7 @@ NetplaySessionDialog::NetplaySessionDialog(QWidget *parent, QWebSocket* webSocke QJsonObject session = json.value("room").toObject(); + this->sessionJson = json.value("room").toObject(); this->nickName = json.value("player_name").toString(); this->sessionPort = session.value("port").toInt(); this->sessionName = session.value("room_name").toString(); @@ -62,6 +65,10 @@ NetplaySessionDialog::NetplaySessionDialog(QWidget *parent, QWebSocket* webSocke QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); startButton->setText("Start"); startButton->setEnabled(false); + + QPushButton* cheatsButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults); + cheatsButton->setText("Cheats"); + cheatsButton->setIcon(QIcon()); } NetplaySessionDialog::~NetplaySessionDialog(void) @@ -72,6 +79,35 @@ NetplaySessionDialog::~NetplaySessionDialog(void) } } +bool NetplaySessionDialog::applyCheats(void) +{ + QJsonObject session = this->sessionJson; + QJsonObject features = session.value("features").toObject(); + QString cheatJson = features["cheats"].toString(); + + if (!cheatJson.isEmpty()) + { + QJsonDocument cheatDocument = QJsonDocument::fromJson(cheatJson.toUtf8()); + QJsonArray cheatArray = cheatDocument.array(); + std::vector cheats; + + if (!CheatsCommon::ParseCheatJson(cheatArray, cheats)) + { + QString error = "Failed to parse cheats json: " + QString(cheatDocument.toJson()); + QtMessageBox::Error(this, "CheatsCommon::ParseCheatJson() Failed", error); + return false; + } + + if (!CoreSetNetplayCheats(cheats)) + { + QtMessageBox::Error(this, "CoreSetNetplayCheats() Failed", QString::fromStdString(CoreGetError())); + return false; + } + } + + return true; +} + void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message) { QJsonDocument jsonDocument = QJsonDocument::fromJson(message.toUtf8()); @@ -91,14 +127,17 @@ void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message) this->listWidget->addItem(name); if (!name.isEmpty()) { - if (this->nickName == name) + bool nameMatch = this->nickName == name; + if (nameMatch) { this->sessionNumber = i + 1; } if (!this->started && i == 0) { QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); - startButton->setEnabled(this->nickName == name); + QPushButton* cheatsButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults); + startButton->setEnabled(nameMatch); + cheatsButton->setEnabled(nameMatch); } } } @@ -113,10 +152,17 @@ void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message) if (json.value("accept").toInt() == 0) { this->started = true; + this->applyCheats(); emit OnPlayGame(this->sessionFile, this->webSocket->peerAddress().toString(), this->sessionPort, this->sessionNumber); } else { + // allow user to try again + QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); + QPushButton* cheatsButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults); + startButton->setEnabled(true); + cheatsButton->setEnabled(true); + QtMessageBox::Error(this, "Server Error", json.value("message").toString()); } } @@ -126,6 +172,17 @@ void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message) this->chatPlainTextEdit->appendHtml(message); this->chatPlainTextEdit->setTextInteractionFlags(Qt::TextBrowserInteraction); } + else if (type == "reply_edit_room") + { + if (json.value("accept").toInt() == 0) + { + this->sessionJson = json.value("room").toObject(); + } + else + { + QtMessageBox::Error(this, "Server Error", json.value("message").toString()); + } + } } void NetplaySessionDialog::on_chatLineEdit_textChanged(QString text) @@ -149,10 +206,65 @@ void NetplaySessionDialog::on_sendPushButton_clicked(void) this->chatLineEdit->clear(); } +void NetplaySessionDialog::on_buttonBox_clicked(QAbstractButton* button) +{ + QPushButton* pushButton = (QPushButton*)button; + QPushButton* cheatsButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults); + + if (pushButton == cheatsButton) + { + QString cheatsJson = this->sessionJson.value("features").toObject().value("cheats").toString(); + QJsonDocument cheatsDocument = QJsonDocument::fromJson(cheatsJson.toUtf8()); + + if (!CoreOpenRom(this->sessionFile.toStdString())) + { + QtMessageBox::Error(this, "CoreOpenRom() Failed", QString::fromStdString(CoreGetError())); + return; + } + + Dialog::CheatsDialog dialog(this, true, cheatsDocument.array()); + dialog.exec(); + + if (!CoreCloseRom()) + { + QtMessageBox::Error(this, "CoreCloseRom() Failed", QString::fromStdString(CoreGetError())); + return; + } + + // request an update to the existing session with the new cheats + cheatsDocument.setArray(dialog.GetJson()); + QJsonObject session = this->sessionJson; + QJsonObject features = session.value("features").toObject(); + if (!cheatsDocument.array().empty()) + { // only add cheats object when it's not empty + features["cheats"] = QString(cheatsDocument.toJson(QJsonDocument::Compact)); + } + else + { // else remove it + features.remove("cheats"); + } + session.insert("features", features); + + // only request update when needed + if (this->sessionJson != session) + { + QJsonObject json; + json.insert("type", "request_edit_room"); + json.insert("player_name", this->nickName); + json.insert("room", session); + NetplayCommon::AddCommonJson(json); + + this->webSocket->sendTextMessage(QJsonDocument(json).toJson()); + } + } +} + void NetplaySessionDialog::accept() { QPushButton* startButton = this->buttonBox->button(QDialogButtonBox::Ok); + QPushButton* cheatsButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults); startButton->setEnabled(false); + cheatsButton->setEnabled(false); QJsonObject json; QJsonObject session; diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp index b1171018..3fd97521 100644 --- a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.hpp @@ -33,18 +33,22 @@ class NetplaySessionDialog : public QDialog, private Ui::NetplaySessionDialog QString sessionFile; QString nickName; QString sessionName; + QJsonObject sessionJson; int sessionPort = -1; int sessionNumber = -1; bool started = false; QWebSocket* webSocket; + bool applyCheats(void); + private slots: void on_webSocket_textMessageReceived(QString message); void on_chatLineEdit_textChanged(QString text); void on_sendPushButton_clicked(void); - + void on_buttonBox_clicked(QAbstractButton* button); + void accept(void) Q_DECL_OVERRIDE; void reject(void) Q_DECL_OVERRIDE; diff --git a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui index f741f92a..2dd170e0 100644 --- a/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui +++ b/Source/RMG/UserInterface/Dialog/Netplay/NetplaySessionDialog.ui @@ -117,7 +117,7 @@ Qt::Orientation::Horizontal - QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::RestoreDefaults false