Improve netplay setup dialog UX

* Focus "Hash Code" / "IP address" text box by default in "Connect"
* Focus game list in "Host" tab
* RETURN keypress now host/join depending on selected tab
* Remember last hosted game
* Remove PanicAlertT:
	* Simply log message to netplay window
	* Remove them when they are useless
* Show some netplay message in OSD
	* Chat messages
	* Pad buffer changes
	* Desync alerts
* Stop the game consistently when another player disconnects / crashes
* Prettify chat textbox
* Log netplay ping to OSD

Join scenario:
* Copy netplay code
* Open netplay
* Paste code
* Press enter

Host scenario:
* Open netplay
* Go to host tab
* Press enter
This commit is contained in:
Aestek 2016-02-02 16:35:27 +01:00
parent aa34e5e20e
commit 6a0fc4c438
13 changed files with 428 additions and 178 deletions

View File

@ -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();
}

View File

@ -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<std::recursive_mutex> 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();

View File

@ -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;

View File

@ -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<std::recursive_mutex> 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<PlayerId, u64> other) {
return other.first == pair.first || other.second != pair.second;
}))
{
if (std::all_of(timebases.begin(), timebases.end(),
[&](std::pair<PlayerId, u64> 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

View File

@ -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"));
@ -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,7 +234,7 @@ 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);
@ -237,37 +250,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 +294,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 +332,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();
@ -354,10 +373,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, &centralPortString);
unsigned long int centralPort;
@ -377,6 +392,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 +408,11 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&)
}
void NetPlaySetupFrame::OnJoin(wxCommandEvent&)
{
DoJoin();
}
void NetPlaySetupFrame::DoJoin()
{
NetPlayDialog*& npd = NetPlayDialog::GetInstance();
if (npd)
@ -406,7 +429,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 +444,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 +480,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 +504,48 @@ 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::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&)

View File

@ -23,12 +23,22 @@ 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 DispatchFocus();
void MakeNetPlayDiag(int port, const std::string& game, bool is_hosting);
@ -39,11 +49,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

View File

@ -12,6 +12,7 @@
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/clipbrd.h>
#include <wx/colour.h>
#include <wx/dialog.h>
#include <wx/frame.h>
#include <wx/listbox.h>
@ -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"));
}

View File

@ -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<int> m_playerids;

View File

@ -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));

View File

@ -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<CallbackType, Callback> s_callbacks;
static std::list<Message> s_msgList;
static std::multimap<MessageType, Message> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(s_messages_mutex);
s_messages.clear();
}
// On-Screen Display Callbacks

View File

@ -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

View File

@ -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);

View File

@ -82,6 +82,8 @@ struct VideoConfig final
// Information
bool bShowFPS;
bool bShowNetPlayPing;
bool bShowNetPlayMessages;
bool bOverlayStats;
bool bOverlayProjStats;
bool bTexFmtOverlayEnable;