From 13fac93a29dc8ef204150f9bfb26b5710193ff2b Mon Sep 17 00:00:00 2001 From: spycrab Date: Fri, 21 Jul 2017 22:48:21 +0200 Subject: [PATCH] Qt: Implement NetPlay --- Source/Core/DolphinQt2/CMakeLists.txt | 5 + Source/Core/DolphinQt2/GameList/GameList.cpp | 18 +- Source/Core/DolphinQt2/GameList/GameList.h | 1 + Source/Core/DolphinQt2/MainWindow.cpp | 159 ++++- Source/Core/DolphinQt2/MainWindow.h | 12 + .../DolphinQt2/NetPlay/GameListDialog.cpp | 70 +++ .../Core/DolphinQt2/NetPlay/GameListDialog.h | 32 + .../Core/DolphinQt2/NetPlay/NetPlayDialog.cpp | 575 ++++++++++++++++++ .../Core/DolphinQt2/NetPlay/NetPlayDialog.h | 110 ++++ .../DolphinQt2/NetPlay/NetPlaySetupDialog.cpp | 267 ++++++++ .../DolphinQt2/NetPlay/NetPlaySetupDialog.h | 72 +++ .../DolphinQt2/NetPlay/PadMappingDialog.cpp | 94 +++ .../DolphinQt2/NetPlay/PadMappingDialog.h | 41 ++ 13 files changed, 1452 insertions(+), 4 deletions(-) create mode 100644 Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp create mode 100644 Source/Core/DolphinQt2/NetPlay/GameListDialog.h create mode 100644 Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp create mode 100644 Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h create mode 100644 Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp create mode 100644 Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h create mode 100644 Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp create mode 100644 Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index 608caaea8b..04726b8434 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -66,6 +66,11 @@ set(SRCS GameList/GridProxyModel.cpp GameList/ListProxyModel.cpp QtUtils/BlockUserInputFilter.cpp + NetPlay/GameListDialog.cpp + NetPlay/MD5Dialog.cpp + NetPlay/NetPlayDialog.cpp + NetPlay/NetPlaySetupDialog.cpp + NetPlay/PadMappingDialog.cpp QtUtils/DoubleClickEventFilter.cpp QtUtils/ElidedButton.cpp QtUtils/ListTabWidget.cpp diff --git a/Source/Core/DolphinQt2/GameList/GameList.cpp b/Source/Core/DolphinQt2/GameList/GameList.cpp index 4cd1794dff..14fd59007c 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.cpp +++ b/Source/Core/DolphinQt2/GameList/GameList.cpp @@ -34,7 +34,7 @@ static bool CompressCB(const std::string&, float, void*); GameList::GameList(QWidget* parent) : QStackedWidget(parent) { - m_model = new GameListModel(this); + m_model = Settings::Instance().GetGameListModel(); m_list_proxy = new ListProxyModel(this); m_list_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); m_list_proxy->setSortRole(Qt::InitialSortOrderRole); @@ -197,6 +197,18 @@ void GameList::ShowContextMenu(const QPoint&) menu->addAction(tr("Open &containing folder"), this, &GameList::OpenContainingFolder); menu->addAction(tr("Delete File..."), this, &GameList::DeleteFile); + + QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu); + + connect(netplay_host, &QAction::triggered, [this, game] { emit NetPlayHost(game); }); + connect(this, &GameList::EmulationStarted, netplay_host, + [netplay_host] { netplay_host->setEnabled(false); }); + connect(this, &GameList::EmulationStopped, netplay_host, + [netplay_host] { netplay_host->setEnabled(true); }); + netplay_host->setEnabled(!Core::IsRunning()); + + menu->addAction(netplay_host); + menu->exec(QCursor::pos()); } @@ -402,12 +414,16 @@ QString GameList::GetSelectedGame() const return QStringLiteral(""); } +<<<<<<< HEAD GameListModel* GameList::GetModel() const { return m_model; } void GameList::SetPreferredView(bool table) +======= +void GameList::SetPreferredView(bool list) +>>>>>>> af63a1c36d... tmp. { m_prefer_list = list; Settings::Instance().SetPreferredView(list); diff --git a/Source/Core/DolphinQt2/GameList/GameList.h b/Source/Core/DolphinQt2/GameList/GameList.h index 6fa65c89de..c7a7be7f89 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.h +++ b/Source/Core/DolphinQt2/GameList/GameList.h @@ -32,6 +32,7 @@ signals: void GameSelected(); void EmulationStarted(); void EmulationStopped(); + void NetPlayHost(const QString& game_path); private: void ShowContextMenu(const QPoint&); diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index cb49592b1e..e3468ee753 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -18,6 +18,7 @@ #include "Core/Boot/Boot.h" #include "Core/BootManager.h" #include "Core/CommonTitles.h" +#include "Core/Config/NetplaySettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/GCKeyboard.h" @@ -27,12 +28,13 @@ #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HotkeyManager.h" #include "Core/Movie.h" +#include "Core/NetPlayClient.h" #include "Core/NetPlayProto.h" +#include "Core/NetPlayServer.h" #include "Core/State.h" #include "DolphinQt2/AboutDialog.h" #include "DolphinQt2/Config/ControllersWindow.h" - #include "DolphinQt2/Config/Graphics/GraphicsWindow.h" #include "DolphinQt2/Config/LoggerWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" @@ -40,6 +42,8 @@ #include "DolphinQt2/Host.h" #include "DolphinQt2/HotkeyScheduler.h" #include "DolphinQt2/MainWindow.h" +#include "DolphinQt2/NetPlay/NetPlayDialog.h" +#include "DolphinQt2/NetPlay/NetPlaySetupDialog.h" #include "DolphinQt2/QtUtils/WindowActivationEventFilter.h" #include "DolphinQt2/Resources.h" #include "DolphinQt2/Settings.h" @@ -71,6 +75,8 @@ MainWindow::MainWindow() : QMainWindow(nullptr) InitControllers(); InitCoreCallbacks(); + + NetPlayInit(); } MainWindow::~MainWindow() @@ -195,6 +201,7 @@ void MainWindow::ConnectMenuBar() // Tools connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate); connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu); + connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog); // View connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView); @@ -258,6 +265,7 @@ void MainWindow::ConnectToolBar() void MainWindow::ConnectGameList() { connect(m_game_list, &GameList::GameSelected, this, &MainWindow::Play); + connect(m_game_list, &GameList::NetPlayHost, this, &MainWindow::NetPlayHost); connect(this, &MainWindow::EmulationStarted, m_game_list, &GameList::EmulationStarted); connect(this, &MainWindow::EmulationStopped, m_game_list, &GameList::EmulationStopped); } @@ -355,8 +363,9 @@ bool MainWindow::RequestStop() if (SConfig::GetInstance().bConfirmStop) { const Core::State state = Core::GetState(); - // TODO: Set to false when Netplay is running as a CPU thread - bool pause = true; + + // Only pause the game, if NetPlay is not running + bool pause = Settings::Instance().GetNetPlayClient() != nullptr; if (pause) Core::SetState(Core::State::Paused); @@ -546,6 +555,13 @@ void MainWindow::ShowGraphicsWindow() m_graphics_window->activateWindow(); } +void MainWindow::ShowNetPlaySetupDialog() +{ + m_netplay_setup_dialog->show(); + m_netplay_setup_dialog->raise(); + m_netplay_setup_dialog->activateWindow(); +} + void MainWindow::StateLoad() { QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(), @@ -616,6 +632,143 @@ void MainWindow::BootWiiSystemMenu() Common::GetTitleContentPath(Titles::SYSTEM_MENU, Common::FROM_CONFIGURED_ROOT))); } +void MainWindow::NetPlayInit() +{ + m_netplay_setup_dialog = new NetPlaySetupDialog(this); + m_netplay_dialog = new NetPlayDialog(this); + + connect(m_netplay_dialog, &NetPlayDialog::Boot, this, &MainWindow::StartGame); + connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::RequestStop); + connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); + connect(this, &MainWindow::EmulationStopped, m_netplay_dialog, &NetPlayDialog::EmulationStopped); + connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); + connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost); +} + +bool MainWindow::NetPlayJoin() +{ + if (Core::IsRunning()) + { + QMessageBox::critical( + nullptr, QObject::tr("Error"), + QObject::tr("Can't start a NetPlay Session while a game is still running!")); + return false; + } + + if (m_netplay_dialog->isVisible()) + { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("A NetPlay Session is already in progress!")); + return false; + } + + // Settings + std::string host_ip, traversal_host, nickname; + int host_port, traversal_port; + bool is_traversal; + if (Settings::Instance().GetNetPlayServer() != nullptr) + { + host_ip = "127.0.0.1"; + host_port = Settings::Instance().GetNetPlayServer()->GetPort(); + } + else + { + host_ip = Config::Get(Config::NETPLAY_HOST_CODE); + host_port = Config::Get(Config::NETPLAY_HOST_PORT); + } + + std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE); + is_traversal = traversal_choice == "traversal"; + + traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER); + traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT); + nickname = Config::Get(Config::NETPLAY_NICKNAME); + + // Create Client + Settings::Instance().ResetNetPlayClient( + new NetPlayClient(host_ip, host_port, m_netplay_dialog, nickname, + Settings::Instance().GetNetPlayServer() != nullptr ? false : is_traversal, + traversal_host, traversal_port)); + + if (!Settings::Instance().GetNetPlayClient()->IsConnected()) + { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("Failed to connect to server")); + return false; + } + + m_netplay_setup_dialog->close(); + m_netplay_dialog->show(nickname, is_traversal); + + return true; +} + +bool MainWindow::NetPlayHost(const QString& game_id) +{ + if (Core::IsRunning()) + { + QMessageBox::critical( + nullptr, QObject::tr("Error"), + QObject::tr("Can't start a NetPlay Session while a game is still running!")); + return false; + } + + if (m_netplay_dialog->isVisible()) + { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("A NetPlay Session is already in progress!")); + return false; + } + + // Settings + std::string traversal_host, nickname; + int host_port, traversal_port; + bool is_traversal, use_upnp; + + host_port = Config::Get(Config::NETPLAY_HOST_PORT); + std::string traversal_choice; + traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE); + is_traversal = traversal_choice == "traversal"; + use_upnp = Config::Get(Config::NETPLAY_USE_UPNP); + + traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER); + traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT); + nickname = Config::Get(Config::NETPLAY_NICKNAME); + + if (is_traversal) + host_port = Config::Get(Config::NETPLAY_LISTEN_PORT); + + // Create Server + Settings::Instance().ResetNetPlayServer( + new NetPlayServer(host_port, is_traversal, traversal_host, traversal_port)); + + if (!Settings::Instance().GetNetPlayServer()->is_connected) + { + QMessageBox::critical( + nullptr, QObject::tr("Failed to open server"), + QObject::tr( + "Failed to listen on port %1. Is another instance of the NetPlay server running?") + .arg(host_port)); + return false; + } + + Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString()); + +#ifdef USE_UPNP + if (use_upnp) + Settings::Instance().GetNetPlayServer()->TryPortmapping(host_port); +#endif + + // Join our local server + return NetPlayJoin(); +} + +void MainWindow::NetPlayQuit() +{ + Settings::Instance().ResetNetPlayClient(); + Settings::Instance().ResetNetPlayServer(); +} + bool MainWindow::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Close) diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index 1987a95de2..c9804d940d 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -17,6 +17,10 @@ class HotkeyScheduler; class LoggerWidget; class MappingWindow; +class NetPlayClient; +class NetPlayDialog; +class NetPlayServer; +class NetPlaySetupDialog; class SettingsWindow; class ControllersWindow; class DragEnterEvent; @@ -88,6 +92,12 @@ private: void ShowGraphicsWindow(); void ShowAboutDialog(); void ShowHotkeyDialog(); + void ShowNetPlaySetupDialog(); + + void NetPlayInit(); + bool NetPlayJoin(); + bool NetPlayHost(const QString& game_id); + void NetPlayQuit(); void OnStopComplete(); void dragEnterEvent(QDragEnterEvent* event) override; @@ -109,6 +119,8 @@ private: ControllersWindow* m_controllers_window; SettingsWindow* m_settings_window; MappingWindow* m_hotkey_window; + NetPlayDialog* m_netplay_dialog; + NetPlaySetupDialog* m_netplay_setup_dialog; GraphicsWindow* m_graphics_window; LoggerWidget* m_logger_widget; }; diff --git a/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp b/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp new file mode 100644 index 0000000000..71155abab5 --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp @@ -0,0 +1,70 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/NetPlay/GameListDialog.h" + +#include +#include +#include + +#include "DolphinQt2/GameList/GameListModel.h" +#include "DolphinQt2/Settings.h" + +GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + CreateWidgets(); + ConnectWidgets(); +} + +void GameListDialog::CreateWidgets() +{ + m_main_layout = new QVBoxLayout; + m_game_list = new QListWidget; + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok); + m_button_box->setEnabled(false); + + m_main_layout->addWidget(m_game_list); + m_main_layout->addWidget(m_button_box); + + setLayout(m_main_layout); +} + +void GameListDialog::ConnectWidgets() +{ + connect(m_game_list, &QListWidget::itemSelectionChanged, [this] { + int row = m_game_list->currentRow(); + + m_button_box->setEnabled(row != -1); + m_game_id = m_game_list->currentItem()->text(); + }); + connect(m_button_box, &QDialogButtonBox::accepted, this, &GameListDialog::accept); +} + +void GameListDialog::PopulateGameList() +{ + auto* game_list_model = Settings::Instance().GetGameListModel(); + + m_game_list->clear(); + + for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++) + { + auto* item = new QListWidgetItem(game_list_model->GetUniqueID(i)); + m_game_list->addItem(item); + } + + m_game_list->sortItems(); +} + +const QString& GameListDialog::GetSelectedUniqueID() +{ + return m_game_id; +} + +int GameListDialog::exec() +{ + PopulateGameList(); + return QDialog::exec(); +} diff --git a/Source/Core/DolphinQt2/NetPlay/GameListDialog.h b/Source/Core/DolphinQt2/NetPlay/GameListDialog.h new file mode 100644 index 0000000000..d3be480680 --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/GameListDialog.h @@ -0,0 +1,32 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +class GameListModel; +class QVBoxLayout; +class QListWidget; +class QDialogButtonBox; + +class GameListDialog : public QDialog +{ + Q_OBJECT +public: + explicit GameListDialog(QWidget* parent); + + int exec(); + const QString& GetSelectedUniqueID(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + void PopulateGameList(); + + QVBoxLayout* m_main_layout; + QListWidget* m_game_list; + QDialogButtonBox* m_button_box; + QString m_game_id; +}; diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp new file mode 100644 index 0000000000..e0c16eedd3 --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp @@ -0,0 +1,575 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/NetPlay/NetPlayDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/CommonPaths.h" +#include "Common/TraversalClient.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "Core/NetPlayServer.h" +#include "DolphinQt2/GameList/GameList.h" +#include "DolphinQt2/NetPlay/GameListDialog.h" +#include "DolphinQt2/NetPlay/MD5Dialog.h" +#include "DolphinQt2/NetPlay/PadMappingDialog.h" +#include "DolphinQt2/QtUtils/QueueOnObject.h" +#include "DolphinQt2/QtUtils/RunOnObject.h" +#include "DolphinQt2/Settings.h" +#include "VideoCommon/VideoConfig.h" + +NetPlayDialog::NetPlayDialog(QWidget* parent) + : QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel()) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + setWindowTitle(tr("Dolphin NetPlay")); + + m_pad_mapping = new PadMappingDialog(this); + m_md5_dialog = new MD5Dialog(this); + + CreateChatLayout(); + CreatePlayersLayout(); + CreateMainLayout(); + ConnectWidgets(); +} + +void NetPlayDialog::CreateMainLayout() +{ + m_main_layout = new QGridLayout; + m_game_button = new QPushButton; + m_md5_box = new QComboBox; + m_start_button = new QPushButton(tr("Start")); + m_buffer_size_box = new QSpinBox; + m_save_sd_box = new QCheckBox(tr("Write save / SD-Card data")); + m_load_wii_box = new QCheckBox(tr("Load Wii Save")); + m_record_input_box = new QCheckBox(tr("Record inputs")); + m_buffer_label = new QLabel(tr("Buffer:")); + m_quit_button = new QPushButton(tr("Quit")); + + m_game_button->setDefault(false); + m_game_button->setAutoDefault(false); + + for (const QString& text : + {tr("MD5 Check:"), tr("Current game"), tr("Other game"), tr("SD card")}) + m_md5_box->addItem(text); + + m_main_layout->addWidget(m_game_button, 0, 0); + m_main_layout->addWidget(m_md5_box, 0, 1); + m_main_layout->addWidget(m_chat_box, 1, 0); + m_main_layout->addWidget(m_players_box, 1, 1); + + auto* options_widget = new QHBoxLayout; + + options_widget->addWidget(m_start_button); + options_widget->addWidget(m_buffer_label); + options_widget->addWidget(m_buffer_size_box); + options_widget->addWidget(m_save_sd_box); + options_widget->addWidget(m_load_wii_box); + options_widget->addWidget(m_record_input_box); + options_widget->addWidget(m_quit_button); + m_main_layout->addLayout(options_widget, 2, 0, 1, -1, Qt::AlignRight); + + setLayout(m_main_layout); +} + +void NetPlayDialog::CreateChatLayout() +{ + m_chat_box = new QGroupBox(tr("Chat")); + m_chat_edit = new QTextEdit; + m_chat_type_edit = new QLineEdit; + m_chat_send_button = new QPushButton(tr("Send")); + + m_chat_send_button->setDefault(false); + m_chat_send_button->setAutoDefault(false); + + m_chat_edit->setReadOnly(true); + + auto* layout = new QGridLayout; + + layout->addWidget(m_chat_edit, 0, 0, 1, -1); + layout->addWidget(m_chat_type_edit, 1, 0); + layout->addWidget(m_chat_send_button, 1, 1); + + m_chat_box->setLayout(layout); +} + +void NetPlayDialog::CreatePlayersLayout() +{ + m_players_box = new QGroupBox(tr("Players")); + m_room_box = new QComboBox; + m_hostcode_label = new QLabel; + m_hostcode_action_button = new QPushButton(tr("Copy")); + m_players_list = new QListWidget; + m_kick_button = new QPushButton(tr("Kick Player")); + m_assign_ports_button = new QPushButton(tr("Assign Controller Ports")); + + auto* layout = new QGridLayout; + + layout->addWidget(m_room_box, 0, 0); + layout->addWidget(m_hostcode_label, 0, 1); + layout->addWidget(m_hostcode_action_button, 0, 2); + layout->addWidget(m_players_list, 1, 0, 1, -1); + layout->addWidget(m_kick_button, 2, 0, 1, -1); + layout->addWidget(m_assign_ports_button, 3, 0, 1, -1); + + m_players_box->setLayout(layout); +} + +void NetPlayDialog::ConnectWidgets() +{ + // Players + connect(m_room_box, static_cast(&QComboBox::currentIndexChanged), this, + &NetPlayDialog::UpdateGUI); + connect(m_hostcode_action_button, &QPushButton::clicked, [this] { + if (m_is_copy_button_retry && m_room_box->currentIndex() == 0) + g_TraversalClient->ReconnectToServer(); + else + QApplication::clipboard()->setText(m_hostcode_label->text()); + }); + connect(m_players_list, &QListWidget::itemSelectionChanged, [this] { + int row = m_players_list->currentRow(); + m_kick_button->setEnabled(row > 0 && + !m_players_list->currentItem()->data(Qt::UserRole).isNull()); + }); + connect(m_kick_button, &QPushButton::clicked, [this] { + auto id = m_players_list->currentItem()->data(Qt::UserRole).toInt(); + Settings::Instance().GetNetPlayServer()->KickPlayer(id); + }); + connect(m_assign_ports_button, &QPushButton::clicked, [this] { + m_pad_mapping->exec(); + + Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray()); + Settings::Instance().GetNetPlayServer()->SetWiimoteMapping(m_pad_mapping->GetWiimoteArray()); + }); + + // Chat + connect(m_chat_send_button, &QPushButton::clicked, this, &NetPlayDialog::OnChat); + connect(m_chat_type_edit, &QLineEdit::returnPressed, this, &NetPlayDialog::OnChat); + + // Other + connect(m_buffer_size_box, static_cast(&QSpinBox::valueChanged), + [this](int value) { + if (Settings::Instance().GetNetPlayServer() != nullptr) + Settings::Instance().GetNetPlayServer()->AdjustPadBufferSize(value); + }); + + connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart); + connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject); + connect(m_md5_box, static_cast(&QComboBox::currentIndexChanged), this, + &NetPlayDialog::OnMD5Combo); + + connect(m_game_button, &QPushButton::clicked, [this] { + GameListDialog gld(this); + if (gld.exec() == QDialog::Accepted) + { + auto unique_id = gld.GetSelectedUniqueID(); + Settings::Instance().GetNetPlayServer()->ChangeGame(unique_id.toStdString()); + } + }); + + connect(this, &NetPlayDialog::EmulationStopped, this, [this] { + if (isVisible()) + GameStatusChanged(false); + }); +} + +void NetPlayDialog::OnChat() +{ + QueueOnObject(this, [this] { + auto msg = m_chat_type_edit->text().toStdString(); + Settings::Instance().GetNetPlayClient()->SendChatMessage(msg); + m_chat_type_edit->clear(); + + DisplayMessage(QStringLiteral("%1: %2").arg(QString::fromStdString(m_nickname), + QString::fromStdString(msg)), + "blue"); + }); +} + +void NetPlayDialog::OnStart() +{ + if (!Settings::Instance().GetNetPlayClient()->DoAllPlayersHaveGame()) + { + if (QMessageBox::question(this, tr("Warning"), + tr("Not all players have the game. Do you really want to start?")) == + QMessageBox::No) + return; + } + + NetSettings settings; + + // Copy all relevant settings + SConfig& instance = SConfig::GetInstance(); + settings.m_CPUthread = instance.bCPUThread; + settings.m_CPUcore = instance.iCPUCore; + settings.m_EnableCheats = instance.bEnableCheats; + settings.m_SelectedLanguage = instance.SelectedLanguage; + settings.m_OverrideGCLanguage = instance.bOverrideGCLanguage; + settings.m_ProgressiveScan = instance.bProgressive; + settings.m_PAL60 = instance.bPAL60; + settings.m_DSPHLE = instance.bDSPHLE; + settings.m_DSPEnableJIT = instance.m_DSPEnableJIT; + settings.m_WriteToMemcard = m_save_sd_box->isChecked(); + settings.m_CopyWiiSave = m_load_wii_box->isChecked(); + settings.m_OCEnable = instance.m_OCEnable; + settings.m_OCFactor = instance.m_OCFactor; + settings.m_EXIDevice[0] = instance.m_EXIDevice[0]; + settings.m_EXIDevice[1] = instance.m_EXIDevice[1]; + + Settings::Instance().GetNetPlayServer()->SetNetSettings(settings); + Settings::Instance().GetNetPlayServer()->StartGame(); +} + +void NetPlayDialog::OnMD5Combo(int index) +{ + std::string identifier; + + switch (index) + { + case 0: + return; + case 1: // Current game + identifier = m_current_game; + break; + case 2: // Other game + { + GameListDialog gld(this); + + if (gld.exec() == QDialog::Accepted) + { + identifier = gld.GetSelectedUniqueID().toStdString(); + break; + } + else + { + m_md5_box->setCurrentIndex(0); + return; + } + } + case 3: // SD Card + identifier = WII_SDCARD; + break; + } + + Settings::Instance().GetNetPlayServer()->ComputeMD5(identifier); +} + +void NetPlayDialog::reject() +{ + if (QMessageBox::question(this, tr("Confirmation"), + tr("Are you sure you want to quit NetPlay?")) == QMessageBox::Yes) + { + QDialog::reject(); + } +} + +void NetPlayDialog::show(std::string nickname, bool use_traversal) +{ + m_nickname = nickname; + m_use_traversal = use_traversal; + + m_room_box->clear(); + m_chat_edit->clear(); + m_chat_type_edit->clear(); + + bool is_hosting = Settings::Instance().GetNetPlayServer() != nullptr; + + if (is_hosting) + { + if (use_traversal) + m_room_box->addItem(tr("Room ID")); + + for (const auto& iface : Settings::Instance().GetNetPlayServer()->GetInterfaceSet()) + m_room_box->addItem(QString::fromStdString(iface)); + } + + m_start_button->setHidden(!is_hosting); + m_save_sd_box->setHidden(!is_hosting); + m_load_wii_box->setHidden(!is_hosting); + m_buffer_size_box->setHidden(!is_hosting); + m_buffer_label->setHidden(!is_hosting); + m_kick_button->setHidden(!is_hosting); + m_assign_ports_button->setHidden(!is_hosting); + m_md5_box->setHidden(!is_hosting); + m_room_box->setHidden(!is_hosting); + m_hostcode_label->setHidden(!is_hosting); + m_hostcode_action_button->setHidden(!is_hosting); + m_game_button->setEnabled(is_hosting); + m_kick_button->setEnabled(false); + + QDialog::show(); + UpdateGUI(); +} + +void NetPlayDialog::UpdateGUI() +{ + // Update player list + std::vector player_ids; + std::string tmp; + + Settings::Instance().GetNetPlayClient()->GetPlayerList(tmp, player_ids); + + std::istringstream ss(tmp); + + int row = m_players_list->currentRow(); + unsigned int i = 0; + + m_players_list->clear(); + + while (std::getline(ss, tmp)) + { + auto text = QString::fromStdString(tmp); + if (!text.isEmpty()) + { + QListWidgetItem* item = new QListWidgetItem(text); + + if (player_ids.size() > i && !text.startsWith(QStringLiteral("Ping:")) && + !text.startsWith(QStringLiteral("Status:"))) + { + item->setData(Qt::UserRole, player_ids[i]); + i++; + } + m_players_list->addItem(item); + } + } + + if (row != -1) + m_players_list->setCurrentRow(row, QItemSelectionModel::SelectCurrent); + + // Update Room ID / IP label + if (m_use_traversal && m_room_box->currentIndex() == 0) + { + switch (g_TraversalClient->m_State) + { + case TraversalClient::Connecting: + m_hostcode_label->setText(tr("...")); + m_hostcode_action_button->setEnabled(false); + break; + case TraversalClient::Connected: + m_hostcode_label->setText(QString::fromStdString( + std::string(g_TraversalClient->m_HostId.data(), g_TraversalClient->m_HostId.size()))); + m_hostcode_action_button->setEnabled(true); + m_hostcode_action_button->setText(tr("Copy")); + m_is_copy_button_retry = false; + break; + case TraversalClient::Failure: + m_hostcode_label->setText(tr("Error")); + m_hostcode_action_button->setText(tr("Retry")); + m_hostcode_action_button->setEnabled(true); + m_is_copy_button_retry = true; + break; + } + } + else if (Settings::Instance().GetNetPlayServer()) + { + m_hostcode_label->setText( + QString::fromStdString(Settings::Instance().GetNetPlayServer()->GetInterfaceHost( + m_room_box->currentText().toStdString()))); + m_hostcode_action_button->setText(tr("Copy")); + m_hostcode_action_button->setEnabled(true); + } +} + +// NetPlayUI methods + +void NetPlayDialog::BootGame(const std::string& filename) +{ + emit Boot(QString::fromStdString(filename)); +} + +void NetPlayDialog::StopGame() +{ + emit Stop(); +} + +void NetPlayDialog::Update() +{ + QueueOnObject(this, [this] { UpdateGUI(); }); +} + +void NetPlayDialog::DisplayMessage(const QString& msg, const std::string& color, int duration) +{ + QueueOnObject(m_chat_edit, [this, color, msg] { + m_chat_edit->append( + QStringLiteral("%2").arg(QString::fromStdString(color), msg)); + }); + + if (g_ActiveConfig.bShowNetPlayMessages && Core::IsRunning()) + { + u32 osd_color; + + // Convert the color string to a OSD color + if (color == "red") + osd_color = OSD::Color::RED; + else if (color == "cyan") + osd_color = OSD::Color::CYAN; + else if (color == "green") + osd_color = OSD::Color::GREEN; + else + osd_color = OSD::Color::YELLOW; + + OSD::AddTypedMessage(OSD::MessageType::NetPlayBuffer, msg.toStdString(), OSD::Duration::NORMAL, + osd_color); + } +} + +void NetPlayDialog::AppendChat(const std::string& msg) +{ + DisplayMessage(QString::fromStdString(msg), ""); +} + +void NetPlayDialog::OnMsgChangeGame(const std::string& title) +{ + QString qtitle = QString::fromStdString(title); + QueueOnObject(this, [this, qtitle, title] { + m_game_button->setText(qtitle); + m_current_game = title; + }); + DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "pink"); +} + +void NetPlayDialog::GameStatusChanged(bool running) +{ + QueueOnObject(this, [this, running] { + if (Settings::Instance().GetNetPlayServer() != nullptr) + { + m_start_button->setEnabled(!running); + m_game_button->setEnabled(!running); + m_load_wii_box->setEnabled(!running); + m_save_sd_box->setEnabled(!running); + m_assign_ports_button->setEnabled(!running); + } + + m_record_input_box->setEnabled(!running); + }); +} + +void NetPlayDialog::OnMsgStartGame() +{ + DisplayMessage(tr("Started game"), "green"); + GameStatusChanged(true); + + QueueOnObject(this, [this] { + Settings::Instance().GetNetPlayClient()->StartGame(FindGame(m_current_game)); + }); +} + +void NetPlayDialog::OnMsgStopGame() +{ + DisplayMessage(tr("Stopped game"), "red"); + GameStatusChanged(false); +} + +void NetPlayDialog::OnPadBufferChanged(u32 buffer) +{ + QueueOnObject(this, [this, buffer] { m_buffer_size_box->setValue(buffer); }); + DisplayMessage(tr("Pad size changed to %1").arg(buffer), "gray"); +} + +void NetPlayDialog::OnDesync(u32 frame, const std::string& player) +{ + DisplayMessage(tr("Possible desync detected: %1 might have desynced at frame %2") + .arg(QString::fromStdString(player), QString::number(frame)), + "red", OSD::Duration::VERY_LONG); +} + +void NetPlayDialog::OnConnectionLost() +{ + DisplayMessage(tr("Lost connection to NetPlay server..."), "red"); +} + +void NetPlayDialog::OnTraversalError(int error) +{ + QueueOnObject(this, [this, error] { + switch (error) + { + case TraversalClient::BadHost: + QMessageBox::critical(this, tr("Traversal Error"), tr("Couldn't look up central server")); + QDialog::reject(); + break; + case TraversalClient::VersionTooOld: + QMessageBox::critical(this, tr("Traversal Error"), + tr("Dolphin is too old for traversal server")); + QDialog::reject(); + break; + case TraversalClient::ServerForgotAboutUs: + case TraversalClient::SocketSendError: + case TraversalClient::ResendTimeout: + UpdateGUI(); + break; + } + }); +} + +bool NetPlayDialog::IsRecording() +{ + return RunOnObject(this, [this] { return m_record_input_box->isChecked(); }); +} + +std::string NetPlayDialog::FindGame(const std::string& game) +{ + return RunOnObject(this, [this, game] { + for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) + { + if (m_game_list_model->GetUniqueID(i).toStdString() == game) + return m_game_list_model->GetPath(i).toStdString(); + } + return std::string(""); + }); +} + +void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier) +{ + QueueOnObject(this, [this, file_identifier] { + m_md5_box->setEnabled(false); + m_md5_box->setCurrentIndex(0); + + if (m_md5_dialog->isVisible()) + m_md5_dialog->close(); + + m_md5_dialog->show(QString::fromStdString(file_identifier)); + }); +} + +void NetPlayDialog::SetMD5Progress(int pid, int progress) +{ + QueueOnObject(this, [this, pid, progress] { + if (m_md5_dialog->isVisible()) + m_md5_dialog->SetProgress(pid, progress); + }); +} + +void NetPlayDialog::SetMD5Result(int pid, const std::string& result) +{ + QueueOnObject(this, [this, pid, result] { + m_md5_dialog->SetResult(pid, result); + m_md5_box->setEnabled(true); + }); +} + +void NetPlayDialog::AbortMD5() +{ + QueueOnObject(this, [this] { + m_md5_dialog->close(); + m_md5_box->setEnabled(true); + }); +} diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h new file mode 100644 index 0000000000..9915ab7940 --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h @@ -0,0 +1,110 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Core/NetPlayClient.h" +#include "VideoCommon/OnScreenDisplay.h" + +class MD5Dialog; +class GameListModel; +class NetPlayServer; +class PadMappingDialog; +class QCheckBox; +class QComboBox; +class QGridLayout; +class QGroupBox; +class QLabel; +class QLineEdit; +class QListWidget; +class QPushButton; +class QSpinBox; +class QTextEdit; + +class NetPlayDialog : public QDialog, public NetPlayUI +{ + Q_OBJECT +public: + NetPlayDialog(QWidget* parent); + + void show(std::string nickname, bool use_traversal); + void reject() override; + + // NetPlayUI methods + void BootGame(const std::string& filename) override; + void StopGame() override; + + void Update() override; + void AppendChat(const std::string& msg) override; + + void OnMsgChangeGame(const std::string& filename) override; + void OnMsgStartGame() override; + void OnMsgStopGame() override; + void OnPadBufferChanged(u32 buffer) override; + void OnDesync(u32 frame, const std::string& player) override; + void OnConnectionLost() override; + void OnTraversalError(int error) override; + bool IsRecording() override; + std::string FindGame(const std::string& game) override; + void ShowMD5Dialog(const std::string& file_identifier) override; + void SetMD5Progress(int pid, int progress) override; + void SetMD5Result(int pid, const std::string& result) override; + void AbortMD5() override; +signals: + void EmulationStopped(); + void Boot(const QString& filename); + void Stop(); + +private: + void CreateChatLayout(); + void CreatePlayersLayout(); + void CreateMainLayout(); + void ConnectWidgets(); + void OnChat(); + void OnStart(); + void OnMD5Combo(int index); + void DisplayMessage(const QString& msg, const std::string& color, + int duration = OSD::Duration::NORMAL); + void UpdateGUI(); + void GameStatusChanged(bool running); + + void SetGame(const QString& game_path); + + // Chat + QGroupBox* m_chat_box; + QTextEdit* m_chat_edit; + QLineEdit* m_chat_type_edit; + QPushButton* m_chat_send_button; + + // Players + QGroupBox* m_players_box; + QComboBox* m_room_box; + QLabel* m_hostcode_label; + QPushButton* m_hostcode_action_button; + QListWidget* m_players_list; + QPushButton* m_kick_button; + QPushButton* m_assign_ports_button; + + // Other + QPushButton* m_game_button; + QComboBox* m_md5_box; + QPushButton* m_start_button; + QLabel* m_buffer_label; + QSpinBox* m_buffer_size_box; + QCheckBox* m_save_sd_box; + QCheckBox* m_load_wii_box; + QCheckBox* m_record_input_box; + QPushButton* m_quit_button; + + QGridLayout* m_main_layout; + MD5Dialog* m_md5_dialog; + PadMappingDialog* m_pad_mapping; + std::string m_current_game; + std::string m_nickname; + GameListModel* m_game_list_model = nullptr; + bool m_use_traversal = false; + bool m_is_copy_button_retry = false; +}; diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp new file mode 100644 index 0000000000..1179d2c06f --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp @@ -0,0 +1,267 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/NetPlay/NetPlaySetupDialog.h" + +#include "Core/Config/NetplaySettings.h" +#include "DolphinQt2/GameList/GameListModel.h" +#include "DolphinQt2/Settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent) + : QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel()) +{ + setWindowTitle(tr("Dolphin NetPlay Setup")); + + CreateMainLayout(); + + std::string nickname = Config::Get(Config::NETPLAY_NICKNAME); + std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE); + int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT); + int host_port = Config::Get(Config::NETPLAY_HOST_PORT); + int host_listen_port = Config::Get(Config::NETPLAY_LISTEN_PORT); +#ifdef USE_UPNP + bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP); + + m_host_upnp->setChecked(use_upnp); +#endif + + m_nickname_edit->setText(QString::fromStdString(nickname)); + m_connection_type->setCurrentIndex(traversal_choice == "direct" ? 0 : 1); + m_connect_port_box->setValue(connect_port); + m_host_port_box->setValue(host_port); + + m_host_force_port_check->setChecked(false); + m_host_force_port_box->setValue(host_listen_port); + m_host_force_port_box->setEnabled(false); + + OnConnectionTypeChanged(m_connection_type->currentIndex()); + + int selected_game = QSettings().value(QStringLiteral("netplay/hostgame"), 0).toInt(); + + if (selected_game >= m_host_games->count()) + selected_game = 0; + + m_host_games->setCurrentItem(m_host_games->item(selected_game)); + + ConnectWidgets(); +} + +void NetPlaySetupDialog::CreateMainLayout() +{ + m_main_layout = new QGridLayout; + m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel); + m_nickname_edit = new QLineEdit; + m_connection_type = new QComboBox; + m_reset_traversal_button = new QPushButton(tr("Reset Traversal Settings")); + m_tab_widget = new QTabWidget; + + // Connection widget + auto* connection_widget = new QWidget; + auto* connection_layout = new QGridLayout; + + m_ip_label = new QLabel; + m_ip_edit = new QLineEdit; + m_connect_port_label = new QLabel(tr("Port:")); + m_connect_port_box = new QSpinBox; + m_connect_button = new QPushButton(tr("Connect")); + + m_connect_port_box->setMaximum(65535); + + connection_layout->addWidget(m_ip_label, 0, 0); + connection_layout->addWidget(m_ip_edit, 0, 1); + connection_layout->addWidget(m_connect_port_label, 0, 2); + connection_layout->addWidget(m_connect_port_box, 0, 3); + connection_layout->addWidget( + new QLabel(tr( + "ALERT:\n\n" + "All players must use the same Dolphin version.\n" + "All memory cards, SD cards and cheats must be identical between players or disabled.\n" + "If DSP LLE is used, DSP ROMs must be identical between players.\n" + "If connecting directly, the host must have the chosen UDP port open/forwarded!\n" + "\n" + "Wii Remote support in netplay is experimental and should not be expected to work.\n")), + 1, 0, -1, -1); + connection_layout->addWidget(m_connect_button, 3, 3, Qt::AlignRight); + + connection_widget->setLayout(connection_layout); + + // Host widget + auto* host_widget = new QWidget; + auto* host_layout = new QGridLayout; + m_host_port_label = new QLabel(tr("Port:")); + m_host_port_box = new QSpinBox; + m_host_force_port_check = new QCheckBox(tr("Force Listen Port:")); + m_host_force_port_box = new QSpinBox; + +#ifdef USE_UPNP + m_host_upnp = new QCheckBox(tr("Forward port (UPnP)")); +#endif + m_host_games = new QListWidget; + m_host_button = new QPushButton(tr("Host")); + + m_host_port_box->setMaximum(65535); + m_host_force_port_box->setMaximum(65535); + + host_layout->addWidget(m_host_port_label, 0, 0); + host_layout->addWidget(m_host_port_box, 0, 1); +#ifdef USE_UPNP + host_layout->addWidget(m_host_upnp, 0, 2); +#endif + host_layout->addWidget(m_host_games, 1, 0, 1, -1); + host_layout->addWidget(m_host_force_port_check, 2, 0); + host_layout->addWidget(m_host_force_port_box, 2, 1, Qt::AlignLeft); + host_layout->addWidget(m_host_button, 2, 2, Qt::AlignRight); + + host_widget->setLayout(host_layout); + + m_connection_type->addItem(tr("Direct Connection")); + m_connection_type->addItem(tr("Traversal Server")); + + m_main_layout->addWidget(new QLabel(tr("Connection Type:")), 0, 0); + m_main_layout->addWidget(m_connection_type, 0, 1); + m_main_layout->addWidget(m_reset_traversal_button, 0, 2); + m_main_layout->addWidget(new QLabel(tr("Nickname:")), 1, 0); + m_main_layout->addWidget(m_nickname_edit, 1, 1); + m_main_layout->addWidget(m_tab_widget, 2, 0, 1, -1); + m_main_layout->addWidget(m_button_box, 3, 0, 1, -1); + + // Tabs + m_tab_widget->addTab(connection_widget, tr("Connect")); + m_tab_widget->addTab(host_widget, tr("Host")); + + setLayout(m_main_layout); +} + +void NetPlaySetupDialog::ConnectWidgets() +{ + connect(m_connection_type, static_cast(&QComboBox::currentIndexChanged), + this, &NetPlaySetupDialog::OnConnectionTypeChanged); + connect(m_nickname_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings); + + // Connect widget + connect(m_ip_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings); + connect(m_connect_port_box, static_cast(&QSpinBox::valueChanged), this, + &NetPlaySetupDialog::SaveSettings); + // Host widget + connect(m_host_port_box, static_cast(&QSpinBox::valueChanged), this, + &NetPlaySetupDialog::SaveSettings); + connect(m_host_games, static_cast(&QListWidget::currentRowChanged), + [](int index) { QSettings().setValue(QStringLiteral("netplay/hostgame"), index); }); + connect(m_host_force_port_check, &QCheckBox::toggled, + [this](int value) { m_host_force_port_box->setEnabled(value); }); +#ifdef USE_UPNP + connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings); +#endif + + connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept); + connect(m_host_button, &QPushButton::clicked, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_reset_traversal_button, &QPushButton::clicked, this, + &NetPlaySetupDialog::ResetTraversalHost); +} + +void NetPlaySetupDialog::SaveSettings() +{ + Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, m_nickname_edit->text().toStdString()); + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, m_ip_edit->text().toStdString()); + Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT, + static_cast(m_connect_port_box->value())); + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_PORT, static_cast(m_host_port_box->value())); + Config::SetBaseOrCurrent(Config::NETPLAY_USE_UPNP, m_host_upnp->isChecked()); + + if (m_host_force_port_check->isChecked()) + Config::SetBaseOrCurrent(Config::NETPLAY_LISTEN_PORT, + static_cast(m_host_force_port_box->value())); +} + +void NetPlaySetupDialog::OnConnectionTypeChanged(int index) +{ + m_connect_port_box->setHidden(index != 0); + m_connect_port_label->setHidden(index != 0); + + m_host_port_label->setHidden(index != 0); + m_host_port_box->setHidden(index != 0); + m_host_upnp->setHidden(index != 0); + m_host_force_port_check->setHidden(index == 0); + m_host_force_port_box->setHidden(index == 0); + + m_reset_traversal_button->setHidden(index == 0); + + std::string address = Config::Get(Config::NETPLAY_HOST_CODE); + + m_ip_label->setText(index == 0 ? tr("IP Address:") : tr("Host Code:")); + m_ip_edit->setText(QString::fromStdString(address)); + + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, + std::string(index == 0 ? "direct" : "traversal")); +} + +void NetPlaySetupDialog::show() +{ + PopulateGameList(); + QDialog::show(); +} + +void NetPlaySetupDialog::accept() +{ + SaveSettings(); + if (m_tab_widget->currentIndex() == 0) + { + emit Join(); + } + else + { + auto items = m_host_games->selectedItems(); + if (items.size() == 0) + { + QMessageBox::critical(this, tr("Error"), tr("You must select a game to host!")); + return; + } + + emit Host(items[0]->text()); + } +} + +void NetPlaySetupDialog::PopulateGameList() +{ + m_host_games->clear(); + for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) + { + auto title = m_game_list_model->GetUniqueID(i); + auto path = m_game_list_model->GetPath(i); + + auto* item = new QListWidgetItem(title); + item->setData(Qt::UserRole, path); + m_host_games->addItem(item); + } + + m_host_games->sortItems(); +} + +void NetPlaySetupDialog::ResetTraversalHost() +{ + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_SERVER, + Config::NETPLAY_TRAVERSAL_SERVER.default_value); + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_PORT, + Config::NETPLAY_TRAVERSAL_PORT.default_value); + + QMessageBox::information( + this, tr("Reset Traversal Server"), + tr("Reset Traversal Server to %1:%2") + .arg(QString::fromStdString(Config::NETPLAY_TRAVERSAL_SERVER.default_value), + QString::number(Config::NETPLAY_TRAVERSAL_PORT.default_value))); +} diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h new file mode 100644 index 0000000000..a73eccf8b1 --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h @@ -0,0 +1,72 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +class GameListModel; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QLabel; +class QLineEdit; +class QListWidget; +class QGridLayout; +class QPushButton; +class QSpinBox; +class QTabWidget; + +class NetPlaySetupDialog : public QDialog +{ + Q_OBJECT +public: + NetPlaySetupDialog(QWidget* parent); + + void accept() override; + void show(); + +signals: + bool Join(); + bool Host(const QString& game_identifier); + +private: + void CreateMainLayout(); + void ConnectWidgets(); + void PopulateGameList(); + void ResetTraversalHost(); + + void SaveSettings(); + + void OnConnectionTypeChanged(int index); + + // Main Widget + QDialogButtonBox* m_button_box; + QComboBox* m_connection_type; + QLineEdit* m_nickname_edit; + QGridLayout* m_main_layout; + QTabWidget* m_tab_widget; + QPushButton* m_reset_traversal_button; + + // Connection Widget + QLabel* m_ip_label; + QLineEdit* m_ip_edit; + QLabel* m_connect_port_label; + QSpinBox* m_connect_port_box; + QPushButton* m_connect_button; + + // Host Widget + QLabel* m_host_port_label; + QSpinBox* m_host_port_box; + QListWidget* m_host_games; + QPushButton* m_host_button; + QCheckBox* m_host_force_port_check; + QSpinBox* m_host_force_port_box; + +#ifdef USE_UPNP + QCheckBox* m_host_upnp; +#endif + + GameListModel* m_game_list_model; +}; diff --git a/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp new file mode 100644 index 0000000000..a149b48b30 --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp @@ -0,0 +1,94 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/NetPlay/PadMappingDialog.h" +#include "DolphinQt2/Settings.h" + +#include "Core/NetPlayClient.h" + +#include +#include +#include +#include + +PadMappingDialog::PadMappingDialog(QWidget* parent) : QDialog(parent) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + CreateWidgets(); + ConnectWidgets(); +} + +void PadMappingDialog::CreateWidgets() +{ + m_main_layout = new QGridLayout; + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok); + + for (unsigned int i = 0; i < m_wii_boxes.size(); i++) + { + m_gc_boxes[i] = new QComboBox; + m_wii_boxes[i] = new QComboBox; + + m_main_layout->addWidget(new QLabel(tr("GC Port %1").arg(i + 1)), 0, i); + m_main_layout->addWidget(new QLabel(tr("Wii Remote Port %1").arg(i + 1)), 0, 4 + i); + m_main_layout->addWidget(m_gc_boxes[i], 1, i); + m_main_layout->addWidget(m_wii_boxes[i], 1, 4 + i); + } + + m_main_layout->addWidget(m_button_box, 2, 0, 1, -1); + + setLayout(m_main_layout); +} + +void PadMappingDialog::ConnectWidgets() +{ + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); +} + +int PadMappingDialog::exec() +{ + m_players = Settings::Instance().GetNetPlayClient()->GetPlayers(); + + QStringList players; + + players.append(tr("None")); + + for (const auto& player : m_players) + players.append(QString::fromStdString(player->name)); + + for (auto& combo_group : {m_gc_boxes, m_wii_boxes}) + { + for (auto& combo : combo_group) + { + combo->clear(); + combo->addItems(players); + connect(combo, static_cast(&QComboBox::currentIndexChanged), this, + &PadMappingDialog::OnMappingChanged); + } + } + + return QDialog::exec(); +} + +PadMappingArray PadMappingDialog::GetGCPadArray() +{ + return m_pad_mapping; +} + +PadMappingArray PadMappingDialog::GetWiimoteArray() +{ + return m_wii_mapping; +} + +void PadMappingDialog::OnMappingChanged() +{ + for (unsigned int i = 0; i < m_wii_boxes.size(); i++) + { + int gc_id = m_gc_boxes[i]->currentIndex(); + int wii_id = m_wii_boxes[i]->currentIndex(); + + m_pad_mapping[i] = gc_id > 0 ? m_players[gc_id - 1]->pid : -1; + m_wii_mapping[i] = wii_id > 0 ? m_players[wii_id - 1]->pid : -1; + } +} diff --git a/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h new file mode 100644 index 0000000000..ebf2c2f48b --- /dev/null +++ b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h @@ -0,0 +1,41 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "Core/NetPlayProto.h" + +class NetPlayClient; +class Player; +class QGridLayout; +class QComboBox; +class QDialogButtonBox; + +class PadMappingDialog : public QDialog +{ + Q_OBJECT +public: + explicit PadMappingDialog(QWidget* widget); + + int exec(); + + PadMappingArray GetGCPadArray(); + PadMappingArray GetWiimoteArray(); + +private: + void CreateWidgets(); + void ConnectWidgets(); + void OnMappingChanged(); + + PadMappingArray m_pad_mapping; + PadMappingArray m_wii_mapping; + + QGridLayout* m_main_layout; + std::array m_gc_boxes; + std::array m_wii_boxes; + std::vector m_players; + QDialogButtonBox* m_button_box; +};