diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp index 81d592d4c3..67b4676769 100644 --- a/Source/Core/Common/TraversalClient.cpp +++ b/Source/Core/Common/TraversalClient.cpp @@ -208,25 +208,6 @@ void TraversalClient::OnFailure(FailureReason reason) m_State = Failure; m_FailureReason = reason; - switch (reason) - { - case TraversalClient::BadHost: - PanicAlertT("Couldn't look up central server %s", m_Server.c_str()); - break; - case TraversalClient::VersionTooOld: - PanicAlertT("Dolphin too old for traversal server"); - break; - case TraversalClient::ServerForgotAboutUs: - PanicAlertT("Disconnected from traversal server"); - break; - case TraversalClient::SocketSendError: - PanicAlertT("Socket error sending to traversal server"); - break; - case TraversalClient::ResendTimeout: - PanicAlertT("Timeout connecting to traversal server"); - break; - } - if (m_Client) m_Client->OnTraversalStateChanged(); } diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 9d974d3342..19c073c020 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -27,6 +27,8 @@ #include "Core/Movie.h" #include "Core/NetPlayClient.h" #include "InputCommon/GCAdapter.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/VideoConfig.h" static std::mutex crit_netplay_client; static NetPlayClient* netplay_client = nullptr; @@ -351,6 +353,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> size; m_target_buffer_size = size; + m_dialog->OnPadBufferChanged(size); } break; @@ -428,17 +431,10 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) break; case NP_MSG_STOP_GAME: - { - m_dialog->OnMsgStopGame(); - } - break; - case NP_MSG_DISABLE_GAME: { - PanicAlertT("Other client disconnected while game is running!! NetPlay is disabled. You must " - "manually stop the game."); - m_is_running.store(false); - NetPlay_Disable(); + StopGame(); + m_dialog->OnMsgStopGame(); } break; @@ -466,6 +462,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> player.ping; } + DisplayPlayersPing(); m_dialog->Update(); } break; @@ -476,18 +473,15 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) u32 frame; packet >> pid_to_blame; packet >> frame; - const char* blame_str = ""; - const char* blame_name = ""; + + std::string player = "??"; std::lock_guard lkp(m_crit.players); - if (pid_to_blame != -1) { auto it = m_players.find(pid_to_blame); - blame_str = " from player "; - blame_name = it != m_players.end() ? it->second.name.c_str() : "??"; + if (it != m_players.end()) + player = it->second.name; } - - m_dialog->AppendChat(StringFromFormat("/!\\ Possible desync detected%s%s on frame %u", - blame_str, blame_name, frame)); + m_dialog->OnDesync(frame, player); } break; @@ -571,6 +565,24 @@ void NetPlayClient::Send(sf::Packet& packet) enet_peer_send(m_server, 0, epac); } +void NetPlayClient::DisplayPlayersPing() +{ + if (!g_ActiveConfig.bShowNetPlayPing) + return; + + OSD::AddTypedMessage(OSD::MessageType::NetPlayPing, + StringFromFormat("Ping: %u", GetPlayersMaxPing()), OSD::Duration::SHORT, + OSD::Color::CYAN); +} + +u32 NetPlayClient::GetPlayersMaxPing() const +{ + return std::max_element( + m_players.begin(), m_players.end(), + [](const auto& a, const auto& b) { return a.second.ping < b.second.ping; }) + ->second.ping; +} + void NetPlayClient::Disconnect() { ENetEvent netEvent; @@ -636,13 +648,11 @@ void NetPlayClient::ThreadFunc() enet_packet_destroy(netEvent.packet); break; case ENET_EVENT_TYPE_DISCONNECT: - m_is_running.store(false); - NetPlay_Disable(); - m_dialog->AppendChat("< LOST CONNECTION TO SERVER >"); - PanicAlertT("Lost connection to server!"); - m_do_loop.store(false); + m_dialog->OnConnectionLost(); + + if (m_is_running.load()) + StopGame(); - netEvent.peer->data = nullptr; break; default: break; @@ -786,8 +796,6 @@ bool NetPlayClient::StartGame(const std::string& path) return false; } - m_dialog->AppendChat(" -- STARTING GAME -- "); - m_timebase_frame = 0; m_is_running.store(true); @@ -895,6 +903,7 @@ void NetPlayClient::OnTraversalStateChanged() m_traversal_client->m_State == TraversalClient::Failure) { Disconnect(); + m_dialog->OnTraversalError(m_traversal_client->m_FailureReason); } } @@ -1080,8 +1089,6 @@ bool NetPlayClient::StopGame() return false; } - m_dialog->AppendChat(" -- STOPPING GAME -- "); - m_is_running.store(false); NetPlay_Disable(); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 196066d457..4eddc36adf 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -32,6 +32,10 @@ public: virtual void OnMsgChangeGame(const std::string& filename) = 0; virtual void OnMsgStartGame() = 0; virtual void OnMsgStopGame() = 0; + virtual void OnPadBufferChanged(u32 buffer) = 0; + virtual void OnDesync(u32 frame, const std::string& player) = 0; + virtual void OnConnectionLost() = 0; + virtual void OnTraversalError(int error) = 0; virtual bool IsRecording() = 0; virtual std::string FindGame(const std::string& game) = 0; virtual void ShowMD5Dialog(const std::string& file_identifier) = 0; @@ -157,6 +161,8 @@ private: void Disconnect(); bool Connect(); void ComputeMD5(const std::string& file_identifier); + void DisplayPlayersPing(); + u32 GetPlayersMaxPing() const; bool m_is_connected = false; ConnectionState m_connection_state = ConnectionState::Failure; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 02173d36a2..0942eae2c1 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -350,15 +350,13 @@ unsigned int NetPlayServer::OnDisconnect(Client& player) { if (mapping == pid && pid != 1) { - PanicAlertT("Client disconnect while game is running!! NetPlay is disabled. You must " - "manually stop the game."); std::lock_guard lkg(m_crit.game); m_is_running = false; sf::Packet spac; spac << (MessageId)NP_MSG_DISABLE_GAME; // this thread doesn't need players lock - SendToClients(spac, 1); + SendToClients(spac, -1); break; } } @@ -636,19 +634,15 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) })) { int pid_to_blame = -1; - if (timebases.size() > 2) + for (auto pair : timebases) { - for (auto pair : timebases) + if (std::all_of(timebases.begin(), timebases.end(), [&](std::pair other) { + return other.first == pair.first || other.second != pair.second; + })) { - if (std::all_of(timebases.begin(), timebases.end(), - [&](std::pair other) { - return other.first == pair.first || other.second != pair.second; - })) - { - // we are the only outlier - pid_to_blame = pair.first; - break; - } + // we are the only outlier + pid_to_blame = pair.first; + break; } } @@ -720,8 +714,8 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) void NetPlayServer::OnTraversalStateChanged() { - if (m_dialog) - m_dialog->Update(); + if (m_dialog && m_traversal_client->m_State == TraversalClient::Failure) + m_dialog->OnTraversalError(m_traversal_client->m_FailureReason); } // called from ---GUI--- thread diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp index 1e845d59bb..a1126429d6 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp @@ -43,10 +43,11 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay Setup")), m_game_list(game_list) { IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); wxPanel* const panel = new wxPanel(this); + panel->Bind(wxEVT_CHAR_HOOK, &NetPlaySetupFrame::OnKeyDown, this); // top row wxBoxSizer* const trav_szr = new wxBoxSizer(wxHORIZONTAL); @@ -57,7 +58,7 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl panel, wxID_ANY, _("Connection Type:"), wxDefaultPosition, wxSize(100, -1)); m_direct_traversal = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(150, -1)); - m_direct_traversal->Bind(wxEVT_CHOICE, &NetPlaySetupFrame::OnChoice, this); + m_direct_traversal->Bind(wxEVT_CHOICE, &NetPlaySetupFrame::OnDirectTraversalChoice, this); m_direct_traversal->Append(_("Direct Connection")); m_direct_traversal->Append(_("Traversal Server")); @@ -90,11 +91,11 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl netplay_section.Get("TraversalChoice", &travChoice, "direct"); if (travChoice == "traversal") { - m_direct_traversal->Select(1); + m_direct_traversal->Select(TRAVERSAL_CHOICE); } else { - m_direct_traversal->Select(0); + m_direct_traversal->Select(DIRECT_CHOICE); } std::string centralPort; @@ -106,19 +107,26 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl centralServer + ":" + centralPort); } // tabs - wxNotebook* const notebook = new wxNotebook(panel, wxID_ANY); - wxPanel* const connect_tab = new wxPanel(notebook, wxID_ANY); - notebook->AddPage(connect_tab, _("Connect")); - wxPanel* const host_tab = new wxPanel(notebook, wxID_ANY); - notebook->AddPage(host_tab, _("Host")); + m_notebook = new wxNotebook(panel, wxID_ANY); + wxPanel* const connect_tab = new wxPanel(m_notebook, wxID_ANY); + m_notebook->AddPage(connect_tab, _("Connect")); + wxPanel* const host_tab = new wxPanel(m_notebook, wxID_ANY); + m_notebook->AddPage(host_tab, _("Host")); // connect tab { m_ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Host Code :")); - std::string address; - netplay_section.Get("HostCode", &address, "00000000"); - m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(address)); + std::string last_hash_code; + netplay_section.Get("HostCode", &last_hash_code, "00000000"); + std::string last_ip_address; + netplay_section.Get("Address", &last_ip_address, "127.0.0.1"); + m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(last_ip_address)); + m_connect_hashcode_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(last_hash_code)); + + // Will be overridden by OnDirectTraversalChoice, but is necessary + // so that both inputs do not take up space + m_connect_hashcode_text->Hide(); m_client_port_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Port :")); @@ -144,7 +152,8 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl top_szr->Add(m_ip_lbl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); top_szr->Add(m_connect_ip_text, 3); - top_szr->Add(m_client_port_lbl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, 5); + top_szr->Add(m_connect_hashcode_text, 3); + top_szr->Add(m_client_port_lbl, 0, wxCENTER | wxRIGHT | wxLEFT, 5); top_szr->Add(m_connect_port_text, 1); wxBoxSizer* const con_szr = new wxBoxSizer(wxVERTICAL); @@ -190,6 +199,10 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl NetPlayDialog::FillWithGameNames(m_game_lbox, *game_list); + std::string last_hosted_game; + if (netplay_section.Get("SelectedHostGame", &last_hosted_game, "")) + m_game_lbox->SetStringSelection(last_hosted_game); + wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); top_szr->Add(m_host_port_lbl, 0, wxCENTER | wxRIGHT, 5); top_szr->Add(m_host_port_text, 0); @@ -221,11 +234,14 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl main_szr->Add(trav_szr, 0, wxALL | wxALIGN_LEFT, 5); main_szr->Add(nick_szr, 0, wxALL | wxALIGN_LEFT, 5); main_szr->Add(m_traversal_lbl, 0, wxALL | wxALIGN_LEFT, 5); - main_szr->Add(notebook, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); + main_szr->Add(m_notebook, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); main_szr->Add(quit_btn, 0, wxALL | wxALIGN_RIGHT, 5); panel->SetSizerAndFit(main_szr); + // Handle focus on tab changes + panel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &NetPlaySetupFrame::OnTabChanged, this); + // wxBoxSizer* const diag_szr = new wxBoxSizer(wxVERTICAL); // diag_szr->Add(panel, 1, wxEXPAND); // SetSizerAndFit(diag_szr); @@ -237,37 +253,35 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl // Needs to be done last or it set up the spacing on the page correctly wxCommandEvent ev; - OnChoice(ev); + OnDirectTraversalChoice(ev); } NetPlaySetupFrame::~NetPlaySetupFrame() { IniFile inifile; - const std::string dolphin_ini = File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"; + const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); inifile.Load(dolphin_ini); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - std::string travChoice = "traversal"; - if (m_direct_traversal->GetSelection() == 1) - { - netplay_section.Set("TraversalChoice", travChoice); - } - else + std::string travChoice; + switch (m_direct_traversal->GetSelection()) { + case TRAVERSAL_CHOICE: + travChoice = "traversal"; + break; + case DIRECT_CHOICE: travChoice = "direct"; - netplay_section.Set("TraversalChoice", travChoice); + break; } + netplay_section.Set("TraversalChoice", travChoice); netplay_section.Set("Nickname", WxStrToStr(m_nickname_text->GetValue())); - if (m_direct_traversal->GetCurrentSelection() == 0) - { + if (m_direct_traversal->GetCurrentSelection() == DIRECT_CHOICE) netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); - } else - { - netplay_section.Set("HostCode", WxStrToStr(m_connect_ip_text->GetValue())); - } + netplay_section.Set("HostCode", WxStrToStr(m_connect_hashcode_text->GetValue())); + netplay_section.Set("ConnectPort", WxStrToStr(m_connect_port_text->GetValue())); netplay_section.Set("HostPort", WxStrToStr(m_host_port_text->GetValue())); netplay_section.Set("ListenPort", m_traversal_listen_port_enabled->IsChecked() ? @@ -283,21 +297,19 @@ void NetPlaySetupFrame::MakeNetPlayDiag(int port, const std::string& game, bool NetPlayDialog*& npd = NetPlayDialog::GetInstance(); NetPlayClient*& netplay_client = NetPlayDialog::GetNetPlayClient(); + bool trav = !is_hosting && m_direct_traversal->GetCurrentSelection() == TRAVERSAL_CHOICE; + std::string ip; npd = new NetPlayDialog(m_parent, m_game_list, game, is_hosting); if (is_hosting) ip = "127.0.0.1"; + else if (trav) + ip = WxStrToStr(m_connect_hashcode_text->GetValue()); else ip = WxStrToStr(m_connect_ip_text->GetValue()); - bool trav; - if (!is_hosting && m_direct_traversal->GetCurrentSelection() == 1) - trav = true; - else - trav = false; - IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); std::string centralPortString; @@ -323,6 +335,16 @@ void NetPlaySetupFrame::MakeNetPlayDiag(int port, const std::string& game, bool void NetPlaySetupFrame::OnHost(wxCommandEvent&) { + DoHost(); +} + +void NetPlaySetupFrame::DoHost() +{ + IniFile ini_file; + const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); + ini_file.Load(dolphin_ini); + IniFile::Section& netplay_section = *ini_file.GetOrCreateSection("NetPlay"); + NetPlayDialog*& npd = NetPlayDialog::GetInstance(); NetPlayServer*& netplay_server = NetPlayDialog::GetNetPlayServer(); @@ -342,7 +364,7 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) bool trav; unsigned long listen_port = 0; - if (m_direct_traversal->GetCurrentSelection() == 1) + if (m_direct_traversal->GetCurrentSelection() == TRAVERSAL_CHOICE) { trav = true; listen_port = @@ -354,10 +376,6 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) m_host_port_text->GetValue().ToULong(&listen_port); } - IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); - IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - std::string centralPortString; GetTraversalPort(netplay_section, ¢ralPortString); unsigned long int centralPort; @@ -377,6 +395,9 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) #endif MakeNetPlayDiag(netplay_server->GetPort(), game, true); netplay_server->SetNetPlayUI(NetPlayDialog::GetInstance()); + + netplay_section.Set("SelectedHostGame", game); + ini_file.Save(dolphin_ini); } else { @@ -390,6 +411,11 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) } void NetPlaySetupFrame::OnJoin(wxCommandEvent&) +{ + DoJoin(); +} + +void NetPlaySetupFrame::DoJoin() { NetPlayDialog*& npd = NetPlayDialog::GetInstance(); if (npd) @@ -406,7 +432,7 @@ void NetPlaySetupFrame::OnJoin(wxCommandEvent&) void NetPlaySetupFrame::OnResetTraversal(wxCommandEvent& event) { IniFile inifile; - const std::string dolphin_ini = File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"; + const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); inifile.Load(dolphin_ini); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); netplay_section.Set("TraversalServer", (std::string) "stun.dolphin-emu.org"); @@ -421,26 +447,23 @@ void NetPlaySetupFrame::OnTraversalListenPortChanged(wxCommandEvent& event) m_traversal_listen_port->Enable(m_traversal_listen_port_enabled->IsChecked()); } -void NetPlaySetupFrame::OnChoice(wxCommandEvent& event) +void NetPlaySetupFrame::OnDirectTraversalChoice(wxCommandEvent& event) { int sel = m_direct_traversal->GetSelection(); IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - if (sel == 1) + if (sel == TRAVERSAL_CHOICE) { m_traversal_lbl->Show(); m_trav_reset_btn->Show(); + m_connect_hashcode_text->Show(); + m_connect_ip_text->Hide(); // Traversal // client tab { m_ip_lbl->SetLabelText("Host Code: "); - - std::string address; - netplay_section.Get("HostCode", &address, "00000000"); - m_connect_ip_text->SetLabelText(address); - m_client_port_lbl->Hide(); m_connect_port_text->Hide(); } @@ -460,6 +483,8 @@ void NetPlaySetupFrame::OnChoice(wxCommandEvent& event) { m_traversal_lbl->Hide(); m_trav_reset_btn->Hide(); + m_connect_hashcode_text->Hide(); + m_connect_ip_text->Show(); // Direct // Client tab { @@ -482,6 +507,65 @@ void NetPlaySetupFrame::OnChoice(wxCommandEvent& event) m_upnp_chk->Show(); #endif } + m_connect_ip_text->GetParent()->Layout(); + DispatchFocus(); +} + +void NetPlaySetupFrame::OnKeyDown(wxKeyEvent& event) +{ + // Let the event propagate + event.Skip(); + + if (event.GetKeyCode() != wxKeyCode::WXK_RETURN) + return; + + int current_tab = m_notebook->GetSelection(); + + switch (current_tab) + { + case CONNECT_TAB: + DoJoin(); + break; + case HOST_TAB: + DoHost(); + break; + } +} + +void NetPlaySetupFrame::OnTabChanged(wxCommandEvent& event) +{ + // Propagate event + event.Skip(); + + // Delaying action so the first tab order element doesn't override the focus + m_notebook->Bind(wxEVT_IDLE, &NetPlaySetupFrame::OnAfterTabChange, this); +} + +void NetPlaySetupFrame::OnAfterTabChange(wxIdleEvent&) +{ + // Unbinding so we don't hog the idle event + m_notebook->Unbind(wxEVT_IDLE, &NetPlaySetupFrame::OnAfterTabChange, this); + + DispatchFocus(); +} + +void NetPlaySetupFrame::DispatchFocus() +{ + int current_tab = m_notebook->GetSelection(); + + switch (current_tab) + { + case CONNECT_TAB: + if (m_direct_traversal->GetCurrentSelection() == TRAVERSAL_CHOICE) + m_connect_hashcode_text->SetFocus(); + else + m_connect_ip_text->SetFocus(); + break; + + case HOST_TAB: + m_game_lbox->SetFocus(); + break; + } } void NetPlaySetupFrame::OnQuit(wxCommandEvent&) diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h index c2ddb569e1..87fc1468a9 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h @@ -23,12 +23,24 @@ public: ~NetPlaySetupFrame(); private: + static constexpr int CONNECT_TAB = 0; + static constexpr int HOST_TAB = 1; + + static constexpr int DIRECT_CHOICE = 0; + static constexpr int TRAVERSAL_CHOICE = 1; + void OnJoin(wxCommandEvent& event); void OnHost(wxCommandEvent& event); + void DoJoin(); + void DoHost(); void OnQuit(wxCommandEvent& event); - void OnChoice(wxCommandEvent& event); + void OnDirectTraversalChoice(wxCommandEvent& event); void OnResetTraversal(wxCommandEvent& event); void OnTraversalListenPortChanged(wxCommandEvent& event); + void OnKeyDown(wxKeyEvent& event); + void OnTabChanged(wxCommandEvent& event); + void OnAfterTabChange(wxIdleEvent& event); + void DispatchFocus(); void MakeNetPlayDiag(int port, const std::string& game, bool is_hosting); @@ -39,11 +51,13 @@ private: wxTextCtrl* m_host_port_text; wxTextCtrl* m_connect_port_text; wxTextCtrl* m_connect_ip_text; + wxTextCtrl* m_connect_hashcode_text; wxChoice* m_direct_traversal; wxStaticText* m_traversal_lbl; wxButton* m_trav_reset_btn; wxCheckBox* m_traversal_listen_port_enabled; wxSpinCtrl* m_traversal_listen_port; + wxNotebook* m_notebook; wxListBox* m_game_lbox; #ifdef USE_UPNP diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp index bbd5308e03..200597b9ca 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -48,29 +49,13 @@ #include "DolphinWX/WxUtils.h" #include "MD5Dialog.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/VideoConfig.h" + NetPlayServer* NetPlayDialog::netplay_server = nullptr; NetPlayClient* NetPlayDialog::netplay_client = nullptr; NetPlayDialog* NetPlayDialog::npd = nullptr; -static wxString FailureReasonStringForHostLabel(int reason) -{ - switch (reason) - { - case TraversalClient::BadHost: - return _("(Error: Bad host)"); - case TraversalClient::VersionTooOld: - return _("(Error: Dolphin too old)"); - case TraversalClient::ServerForgotAboutUs: - return _("(Error: Disconnected)"); - case TraversalClient::SocketSendError: - return _("(Error: Socket)"); - case TraversalClient::ResendTimeout: - return _("(Error: Timeout)"); - default: - return _("(Error: Unknown)"); - } -} - static std::string BuildGameName(const GameListItem& game) { // Lang needs to be consistent @@ -280,13 +265,13 @@ NetPlayDialog::~NetPlayDialog() void NetPlayDialog::OnChat(wxCommandEvent&) { - wxString text = m_chat_msg_text->GetValue(); + std::string text = WxStrToStr(m_chat_msg_text->GetValue()); if (!text.empty()) { - netplay_client->SendChatMessage(WxStrToStr(text)); - m_chat_text->AppendText(text.Prepend(" >> ").Append('\n')); + netplay_client->SendChatMessage(text); m_chat_msg_text->Clear(); + AddChatMessage(ChatMessageType::UserOut, text); } } @@ -406,11 +391,46 @@ void NetPlayDialog::OnAdjustBuffer(wxCommandEvent& event) { const int val = ((wxSpinCtrl*)event.GetEventObject())->GetValue(); netplay_server->AdjustPadBufferSize(val); +} - std::ostringstream ss; - ss << "< Pad Buffer: " << val << " >"; - netplay_client->SendChatMessage(ss.str()); - m_chat_text->AppendText(StrToWxStr(ss.str()).Append('\n')); +void NetPlayDialog::OnPadBufferChanged(u32 buffer) +{ + m_pad_buffer = buffer; + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_PAD_BUFFER_CHANGE); + GetEventHandler()->AddPendingEvent(evt); +} + +void NetPlayDialog::OnDesync(u32 frame, const std::string& player) +{ + m_desync_frame = frame; + m_desync_player = player; + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_DESYNC); + GetEventHandler()->AddPendingEvent(evt); +} + +void NetPlayDialog::OnConnectionLost() +{ + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_CONNECTION_LOST); + GetEventHandler()->AddPendingEvent(evt); +} + +void NetPlayDialog::OnTraversalError(int error) +{ + switch (error) + { + case TraversalClient::BadHost: + PanicAlertT("Couldn't look up central server"); + break; + case TraversalClient::VersionTooOld: + PanicAlertT("Dolphin is too old for traversal server"); + break; + case TraversalClient::ServerForgotAboutUs: + case TraversalClient::SocketSendError: + case TraversalClient::ResendTimeout: + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_TRAVERSAL_CONNECTION_ERROR); + GetEventHandler()->AddPendingEvent(evt); + break; + } } void NetPlayDialog::OnQuit(wxCommandEvent&) @@ -477,17 +497,16 @@ void NetPlayDialog::OnThread(wxThreadEvent& event) case NP_GUI_EVT_START_GAME: // client start game :/ { - std::string game = FindCurrentGame(); - if (game.empty()) - WxUtils::ShowErrorDialog(_("Game not found!")); - else - netplay_client->StartGame(game); + netplay_client->StartGame(FindCurrentGame()); + std::string msg = "Starting game"; + AddChatMessage(ChatMessageType::Info, msg); } break; case NP_GUI_EVT_STOP_GAME: // client stop game { - netplay_client->StopGame(); + std::string msg = "Stopping game"; + AddChatMessage(ChatMessageType::Info, msg); } break; case NP_GUI_EVT_DISPLAY_MD5_DIALOG: @@ -515,6 +534,42 @@ void NetPlayDialog::OnThread(wxThreadEvent& event) m_MD5_dialog->SetResult(payload.first, payload.second); } break; + case NP_GUI_EVT_PAD_BUFFER_CHANGE: + { + std::string msg = StringFromFormat("Pad buffer: %d", m_pad_buffer); + + if (g_ActiveConfig.bShowNetPlayMessages) + { + OSD::AddTypedMessage(OSD::MessageType::NetPlayBuffer, msg, OSD::Duration::NORMAL); + } + + AddChatMessage(ChatMessageType::Info, msg); + } + break; + case NP_GUI_EVT_DESYNC: + { + std::string msg = "Possible desync detected from player " + m_desync_player + " on frame " + + std::to_string(m_desync_frame); + + AddChatMessage(ChatMessageType::Error, msg); + + if (g_ActiveConfig.bShowNetPlayMessages) + { + OSD::AddMessage(msg, OSD::Duration::VERY_LONG, OSD::Color::RED); + } + } + break; + case NP_GUI_EVT_CONNECTION_LOST: + { + std::string msg = "Lost connection to server"; + AddChatMessage(ChatMessageType::Error, msg); + } + break; + case NP_GUI_EVT_TRAVERSAL_CONNECTION_ERROR: + { + std::string msg = "Traversal server connection error"; + AddChatMessage(ChatMessageType::Error, msg); + } } // chat messages @@ -522,8 +577,12 @@ void NetPlayDialog::OnThread(wxThreadEvent& event) { std::string s; chat_msgs.Pop(s); - // PanicAlert("message: %s", s.c_str()); - m_chat_text->AppendText(StrToWxStr(s).Append('\n')); + AddChatMessage(ChatMessageType::UserIn, s); + + if (g_ActiveConfig.bShowNetPlayMessages) + { + OSD::AddMessage(s, OSD::Duration::NORMAL, OSD::Color::GREEN); + } } } @@ -690,7 +749,7 @@ void NetPlayDialog::UpdateHostLabel() break; case TraversalClient::Failure: m_host_label->SetForegroundColour(*wxBLACK); - m_host_label->SetLabel(FailureReasonStringForHostLabel(g_TraversalClient->m_FailureReason)); + m_host_label->SetLabel("..."); m_host_copy_btn->SetLabel(_("Retry")); m_host_copy_btn->Enable(); m_host_copy_btn_is_retry = true; @@ -724,3 +783,36 @@ void NetPlayDialog::UpdateHostLabel() } } } + +void NetPlayDialog::AddChatMessage(ChatMessageType type, const std::string& msg) +{ + wxColour colour = *wxBLACK; + std::string printed_msg = msg; + + switch (type) + { + case ChatMessageType::Info: + colour = wxColour(0, 150, 150); // cyan + break; + + case ChatMessageType::Error: + colour = *wxRED; + break; + + case ChatMessageType::UserIn: + colour = wxColour(0, 150, 0); // green + printed_msg = "▶ " + msg; + break; + + case ChatMessageType::UserOut: + colour = wxColour(100, 100, 100); // grey + printed_msg = "◀ " + msg; + break; + } + + if (type == ChatMessageType::Info || type == ChatMessageType::Error) + printed_msg = "― " + msg + " ―"; + + m_chat_text->SetDefaultStyle(wxTextAttr(colour)); + m_chat_text->AppendText(StrToWxStr(printed_msg + "\n")); +} diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.h b/Source/Core/DolphinWX/NetPlay/NetWindow.h index b7fb088b99..b3cb410440 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.h +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.h @@ -31,6 +31,10 @@ enum NP_GUI_EVT_DISPLAY_MD5_DIALOG, NP_GUI_EVT_MD5_PROGRESS, NP_GUI_EVT_MD5_RESULT, + NP_GUI_EVT_PAD_BUFFER_CHANGE, + NP_GUI_EVT_DESYNC, + NP_GUI_EVT_CONNECTION_LOST, + NP_GUI_EVT_TRAVERSAL_CONNECTION_ERROR, }; enum @@ -38,6 +42,18 @@ enum INITIAL_PAD_BUFFER_SIZE = 5 }; +enum class ChatMessageType +{ + // Info messages logged to chat + Info, + // Error messages logged to chat + Error, + // Incoming user chat messages + UserIn, + // Outcoming user chat messages + UserOut, +}; + // IDs are UI-dependent here enum class MD5Target { @@ -72,6 +88,10 @@ public: 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; static NetPlayDialog*& GetInstance() { return npd; } static NetPlayClient*& GetNetPlayClient() { return netplay_client; } @@ -92,7 +112,8 @@ private: void OnPlayerSelect(wxCommandEvent& event); void GetNetSettings(NetSettings& settings); std::string FindCurrentGame(); - std::string FindGame(const std::string& game) override; + std::string FindGame(const std::string& game); + void AddChatMessage(ChatMessageType type, const std::string& msg); void OnCopyIP(wxCommandEvent&); void OnChoice(wxCommandEvent& event); @@ -116,6 +137,9 @@ private: MD5Dialog* m_MD5_dialog = nullptr; bool m_host_copy_btn_is_retry; bool m_is_hosting; + u32 m_pad_buffer; + u32 m_desync_frame; + std::string m_desync_player; std::vector m_playerids; diff --git a/Source/Core/DolphinWX/VideoConfigDiag.cpp b/Source/Core/DolphinWX/VideoConfigDiag.cpp index 9378d6fd7d..86d37f52ed 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.cpp +++ b/Source/Core/DolphinWX/VideoConfigDiag.cpp @@ -201,13 +201,20 @@ static wxString disable_fog_desc = "detail.\nDisabling fog will break some games which rely on proper fog " "emulation.\n\nIf unsure, leave this unchecked."); static wxString show_fps_desc = - wxTRANSLATE("Show the number of frames rendered per second as a measure of emulation " - "speed.\n\nIf unsure, leave this unchecked."); -static wxString log_render_time_to_file_desc = wxTRANSLATE( - "Log the render time of every frame to User/Logs/render_time.txt. Use this feature when you " - "want to measure the performance of Dolphin.\n\nIf unsure, leave this unchecked."); + wxTRANSLATE("Show the number of frames rendered per second as a measure of " + "emulation speed.\n\nIf unsure, leave this unchecked."); +static wxString show_netplay_ping_desc = + wxTRANSLATE("Show the players' maximum Ping while playing on " + "NetPlay.\n\nIf unsure, leave this unchecked."); +static wxString log_render_time_to_file_desc = + wxTRANSLATE("Log the render time of every frame to User/Logs/render_time.txt. Use this " + "feature when you want to measure the performance of Dolphin.\n\nIf " + "unsure, leave this unchecked."); static wxString show_stats_desc = wxTRANSLATE("Show various rendering statistics.\n\nIf unsure, leave this unchecked."); +static wxString show_netplay_messages_desc = + wxTRANSLATE("When playing on NetPlay, show chat messages, buffer changes and " + "desync alerts.\n\nIf unsure, leave this unchecked."); static wxString texfmt_desc = wxTRANSLATE("Modify textures to show the format they're encoded in. Needs an emulation reset " "in most cases.\n\nIf unsure, leave this unchecked."); @@ -442,12 +449,18 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title) { szr_other->Add(CreateCheckBox(page_general, _("Show FPS"), wxGetTranslation(show_fps_desc), vconfig.bShowFPS)); + szr_other->Add(CreateCheckBox(page_general, _("Show NetPlay Ping"), + wxGetTranslation(show_netplay_ping_desc), + vconfig.bShowNetPlayPing)); szr_other->Add(CreateCheckBox(page_general, _("Log Render Time to File"), wxGetTranslation(log_render_time_to_file_desc), vconfig.bLogRenderTimeToFile)); szr_other->Add(CreateCheckBox(page_general, _("Auto adjust Window Size"), wxGetTranslation(auto_window_size_desc), SConfig::GetInstance().bRenderWindowAutoSize)); + szr_other->Add(CreateCheckBox(page_general, _("Show NetPlay Messages"), + wxGetTranslation(show_netplay_messages_desc), + vconfig.bShowNetPlayMessages)); szr_other->Add(CreateCheckBox(page_general, _("Keep Window on Top"), wxGetTranslation(keep_window_on_top_desc), SConfig::GetInstance().bKeepWindowOnTop)); diff --git a/Source/Core/VideoCommon/OnScreenDisplay.cpp b/Source/Core/VideoCommon/OnScreenDisplay.cpp index 3cc2e823d8..62997cd150 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.cpp +++ b/Source/Core/VideoCommon/OnScreenDisplay.cpp @@ -17,21 +17,30 @@ namespace OSD { -struct Message -{ - Message() {} - Message(const std::string& s, u32 ts, u32 rgba) : m_str(s), m_timestamp(ts), m_rgba(rgba) {} - std::string m_str; - u32 m_timestamp; - u32 m_rgba; -}; - static std::multimap s_callbacks; -static std::list s_msgList; +static std::multimap s_messages; +static std::mutex s_messages_mutex; -void AddMessage(const std::string& str, u32 ms, u32 rgba) +void AddTypedMessage(MessageType type, const std::string& message, u32 ms, u32 rgba) { - s_msgList.emplace_back(str, Common::Timer::GetTimeMs() + ms, rgba); + std::lock_guard lock(s_messages_mutex); + s_messages.erase(type); + s_messages.emplace(type, Message(message, Common::Timer::GetTimeMs() + ms, rgba)); +} + +void AddMessage(const std::string& message, u32 ms, u32 rgba) +{ + std::lock_guard lock(s_messages_mutex); + s_messages.emplace(MessageType::Typeless, + Message(message, Common::Timer::GetTimeMs() + ms, rgba)); +} + +void DrawMessage(const Message& msg, int top, int left, int time_left) +{ + float alpha = std::min(1.0f, std::max(0.0f, time_left / 1024.0f)); + u32 color = (msg.m_rgba & 0xFFFFFF) | ((u32)((msg.m_rgba >> 24) * alpha) << 24); + + g_renderer->RenderText(msg.m_str, left, top, color); } void DrawMessages() @@ -39,28 +48,32 @@ void DrawMessages() if (!SConfig::GetInstance().bOnScreenDisplayMessages) return; - int left = 25, top = 15; - auto it = s_msgList.begin(); - while (it != s_msgList.end()) { - int time_left = (int)(it->m_timestamp - Common::Timer::GetTimeMs()); - float alpha = std::max(1.0f, std::min(0.0f, time_left / 1024.0f)); - u32 color = (it->m_rgba & 0xFFFFFF) | ((u32)((it->m_rgba >> 24) * alpha) << 24); + std::lock_guard lock(s_messages_mutex); - g_renderer->RenderText(it->m_str, left, top, color); + u32 now = Common::Timer::GetTimeMs(); + int left = 20, top = 35; - top += 15; + auto it = s_messages.begin(); + while (it != s_messages.end()) + { + const Message& msg = it->second; + int time_left = (int)(msg.m_timestamp - now); + DrawMessage(msg, top, left, time_left); - if (time_left <= 0) - it = s_msgList.erase(it); - else - ++it; + if (time_left <= 0) + it = s_messages.erase(it); + else + ++it; + top += 15; + } } } void ClearMessages() { - s_msgList.clear(); + std::lock_guard lock(s_messages_mutex); + s_messages.clear(); } // On-Screen Display Callbacks diff --git a/Source/Core/VideoCommon/OnScreenDisplay.h b/Source/Core/VideoCommon/OnScreenDisplay.h index ab74201bca..a05f4058ef 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.h +++ b/Source/Core/VideoCommon/OnScreenDisplay.h @@ -11,9 +11,47 @@ namespace OSD { +struct Message +{ + Message() {} + Message(const std::string& s, u32 ts, u32 rgba) : m_str(s), m_timestamp(ts), m_rgba(rgba) {} + std::string m_str; + u32 m_timestamp; + u32 m_rgba; +}; + +enum class MessageType +{ + NetPlayPing, + NetPlayBuffer, + + // This entry must be kept last so that persistent typed messages are + // displayed before other messages + Typeless, +}; + +namespace Color +{ +constexpr u32 CYAN = 0xFF00FFFF; +constexpr u32 GREEN = 0xFF00FF00; +constexpr u32 RED = 0xFFFF0000; +constexpr u32 YELLOW = 0xFFFFFF30; +}; + +namespace Duration +{ +constexpr u32 SHORT = 2000; +constexpr u32 NORMAL = 5000; +constexpr u32 VERY_LONG = 10000; +}; + // On-screen message display (colored yellow by default) -void AddMessage(const std::string& str, u32 ms = 2000, u32 rgba = 0xFFFFFF30); -void DrawMessages(); // draw the current messages on the screen. Only call once per frame. +void AddMessage(const std::string& message, u32 ms = Duration::SHORT, u32 rgba = Color::YELLOW); +void AddTypedMessage(MessageType type, const std::string& message, u32 ms = Duration::SHORT, + u32 rgba = Color::YELLOW); +void DrawMessage(const Message& msg, int top, int left, int time_left); // draw one message +void DrawMessages(); // draw the current messages on the screen. Only call once + // per frame. void ClearMessages(); // On-screen callbacks diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index c19528d7d9..7f8def5450 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -60,6 +60,8 @@ void VideoConfig::Load(const std::string& ini_file) settings->Get("UseRealXFB", &bUseRealXFB, 0); settings->Get("SafeTextureCacheColorSamples", &iSafeTextureCache_ColorSamples, 128); settings->Get("ShowFPS", &bShowFPS, false); + settings->Get("ShowNetPlayPing", &bShowNetPlayPing, false); + settings->Get("ShowNetPlayMessages", &bShowNetPlayMessages, false); settings->Get("LogRenderTimeToFile", &bLogRenderTimeToFile, false); settings->Get("OverlayStats", &bOverlayStats, false); settings->Get("OverlayProjStats", &bOverlayProjStats, false); @@ -267,6 +269,8 @@ void VideoConfig::Save(const std::string& ini_file) settings->Set("UseRealXFB", bUseRealXFB); settings->Set("SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples); settings->Set("ShowFPS", bShowFPS); + settings->Set("ShowNetPlayPing", bShowNetPlayPing); + settings->Set("ShowNetPlayMessages", bShowNetPlayMessages); settings->Set("LogRenderTimeToFile", bLogRenderTimeToFile); settings->Set("OverlayStats", bOverlayStats); settings->Set("OverlayProjStats", bOverlayProjStats); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index d78c63ba90..9df952a18d 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -82,6 +82,8 @@ struct VideoConfig final // Information bool bShowFPS; + bool bShowNetPlayPing; + bool bShowNetPlayMessages; bool bOverlayStats; bool bOverlayProjStats; bool bTexFmtOverlayEnable;