// Copyright 2015 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" #include "Common/FifoQueue.h" #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Common/MsgHandler.h" #include "Core/ConfigManager.h" #include "Core/HW/EXI_Device.h" #include "Core/NetPlayClient.h" #include "Core/NetPlayProto.h" #include "Core/NetPlayServer.h" #include "DolphinWX/Frame.h" #include "DolphinWX/GameListCtrl.h" #include "DolphinWX/ISOFile.h" #include "DolphinWX/Main.h" #include "DolphinWX/NetPlay/ChangeGameDialog.h" #include "DolphinWX/NetPlay/NetWindow.h" #include "DolphinWX/NetPlay/PadMapDialog.h" #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; void NetPlayDialog::FillWithGameNames(wxListBox* game_lbox, const CGameListCtrl& game_list) { for (u32 i = 0; auto game = game_list.GetISO(i); ++i) game_lbox->Append(StrToWxStr(game->GetUniqueIdentifier())); } NetPlayDialog::NetPlayDialog(wxWindow* const parent, const CGameListCtrl* const game_list, const std::string& game, const bool is_hosting) : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay")), m_selected_game(game), m_start_btn(nullptr), m_host_label(nullptr), m_host_type_choice(nullptr), m_host_copy_btn(nullptr), m_host_copy_btn_is_retry(false), m_is_hosting(is_hosting), m_game_list(game_list) { Bind(wxEVT_THREAD, &NetPlayDialog::OnThread, this); CreateGUI(); SetIcons(WxUtils::GetDolphinIconBundle()); Center(); // Remember the window size and position for NetWindow { IniFile inifile; inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); int winPosX, winPosY, winWidth, winHeight; netplay_section.Get("NetWindowPosX", &winPosX, std::numeric_limits::min()); netplay_section.Get("NetWindowPosY", &winPosY, std::numeric_limits::min()); netplay_section.Get("NetWindowWidth", &winWidth, -1); netplay_section.Get("NetWindowHeight", &winHeight, -1); WxUtils::SetWindowSizeAndFitToScreen(this, wxPoint(winPosX, winPosY), wxSize(winWidth, winHeight), GetSize()); } } void NetPlayDialog::CreateGUI() { const int space5 = FromDIP(5); // NOTE: The design operates top down. Margins / padding are handled by the outermost // sizers, this makes the design easier to change. Inner sizers should only pad between // widgets, they should never have prepended or appended margins/spacers. wxPanel* const panel = new wxPanel(this); wxBoxSizer* const main_szr = new wxBoxSizer(wxVERTICAL); main_szr->AddSpacer(space5); main_szr->Add(CreateTopGUI(panel), 0, wxEXPAND | wxLEFT | wxRIGHT, space5); main_szr->AddSpacer(space5); main_szr->Add(CreateMiddleGUI(panel), 1, wxEXPAND | wxLEFT | wxRIGHT, space5); main_szr->AddSpacer(space5); main_szr->Add(CreateBottomGUI(panel), 0, wxEXPAND | wxLEFT | wxRIGHT, space5); main_szr->AddSpacer(space5); panel->SetSizerAndFit(main_szr); main_szr->SetSizeHints(this); SetSize(FromDIP(wxSize(768, 768 - 128))); } wxSizer* NetPlayDialog::CreateTopGUI(wxWindow* parent) { m_game_btn = new wxButton(parent, wxID_ANY, _(" Game : ") + StrToWxStr(m_selected_game), wxDefaultPosition, wxDefaultSize, wxBU_LEFT); if (m_is_hosting) m_game_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnChangeGame, this); else m_game_btn->Disable(); wxBoxSizer* top_szr = new wxBoxSizer(wxHORIZONTAL); top_szr->Add(m_game_btn, 1, wxEXPAND); if (m_is_hosting) { m_MD5_choice = new wxChoice(parent, wxID_ANY, wxDefaultPosition, FromDIP(wxSize(150, -1))); m_MD5_choice->Bind(wxEVT_CHOICE, &NetPlayDialog::OnMD5ComputeRequested, this); m_MD5_choice->Append(_("MD5 check...")); m_MD5_choice->Append(_("Current game")); m_MD5_choice->Append(_("Other game")); m_MD5_choice->Append(_("SD card")); m_MD5_choice->SetSelection(0); top_szr->Add(m_MD5_choice, 0, wxALIGN_CENTER_VERTICAL); } return top_szr; } wxSizer* NetPlayDialog::CreateMiddleGUI(wxWindow* parent) { const int space5 = FromDIP(5); wxBoxSizer* const mid_szr = new wxBoxSizer(wxHORIZONTAL); mid_szr->Add(CreateChatGUI(parent), 1, wxEXPAND); mid_szr->Add(CreatePlayerListGUI(parent), 0, wxEXPAND | wxLEFT, space5); return mid_szr; } wxSizer* NetPlayDialog::CreateChatGUI(wxWindow* parent) { const int space5 = FromDIP(5); wxStaticBoxSizer* const chat_szr = new wxStaticBoxSizer(wxVERTICAL, parent, _("Chat")); parent = chat_szr->GetStaticBox(); m_chat_text = new wxTextCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE); m_chat_msg_text = new wxTextCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); m_chat_msg_text->Bind(wxEVT_TEXT_ENTER, &NetPlayDialog::OnChat, this); m_chat_msg_text->SetMaxLength(2000); wxButton* const chat_msg_btn = new wxButton(parent, wxID_ANY, _("Send"), wxDefaultPosition, wxSize(-1, m_chat_msg_text->GetBestSize().GetHeight())); chat_msg_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnChat, this); wxBoxSizer* const chat_msg_szr = new wxBoxSizer(wxHORIZONTAL); // NOTE: Remember that fonts are configurable, setting sizes of anything that contains // text in pixels is dangerous because the text may end up being clipped. chat_msg_szr->Add(WxUtils::GiveMinSizeDIP(m_chat_msg_text, wxSize(-1, 25)), 1, wxALIGN_CENTER_VERTICAL); chat_msg_szr->Add(chat_msg_btn, 0, wxEXPAND); chat_szr->Add(m_chat_text, 1, wxEXPAND); chat_szr->Add(chat_msg_szr, 0, wxEXPAND | wxTOP, space5); return chat_szr; } wxSizer* NetPlayDialog::CreatePlayerListGUI(wxWindow* parent) { const int space5 = FromDIP(5); wxStaticBoxSizer* const player_szr = new wxStaticBoxSizer(wxVERTICAL, parent, _("Players")); // Static box is a widget, new widgets should be children instead of siblings to avoid various // flickering problems. parent = player_szr->GetStaticBox(); m_player_lbox = new wxListBox(parent, wxID_ANY, wxDefaultPosition, FromDIP(wxSize(256, -1)), 0, nullptr, wxLB_HSCROLL); if (m_is_hosting && g_TraversalClient) { m_host_type_choice = new wxChoice(parent, wxID_ANY, wxDefaultPosition, FromDIP(wxSize(76, -1))); m_host_type_choice->Bind(wxEVT_CHOICE, &NetPlayDialog::OnChoice, this); m_host_type_choice->Append(_("Room ID:")); m_host_type_choice->Select(0); m_host_label = new wxStaticText(parent, wxID_ANY, "555.555.555.555:55555", wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE); // Update() should fix this immediately. m_host_label->SetLabel(""); m_host_copy_btn = new wxButton(parent, wxID_ANY, _("Copy")); m_host_copy_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnCopyIP, this); m_host_copy_btn->Disable(); UpdateHostLabel(); wxBoxSizer* const host_szr = new wxBoxSizer(wxHORIZONTAL); host_szr->Add(m_host_type_choice); host_szr->Add(m_host_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, space5); host_szr->Add(m_host_copy_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, space5); player_szr->Add(host_szr, 0, wxEXPAND, space5); } player_szr->Add(m_player_lbox, 1, wxEXPAND); if (m_is_hosting) { m_player_lbox->Bind(wxEVT_LISTBOX, &NetPlayDialog::OnPlayerSelect, this); m_kick_btn = new wxButton(parent, wxID_ANY, _("Kick Player")); m_kick_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnKick, this); m_kick_btn->Disable(); m_player_config_btn = new wxButton(parent, wxID_ANY, _("Assign Controller Ports")); m_player_config_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnAssignPads, this); player_szr->Add(m_kick_btn, 0, wxEXPAND | wxTOP, space5); player_szr->Add(m_player_config_btn, 0, wxEXPAND | wxTOP, space5); } return player_szr; } wxSizer* NetPlayDialog::CreateBottomGUI(wxWindow* parent) { const int space5 = FromDIP(5); wxBoxSizer* const bottom_szr = new wxBoxSizer(wxHORIZONTAL); if (m_is_hosting) { m_start_btn = new wxButton(parent, wxID_ANY, _("Start")); m_start_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnStart, this); wxStaticText* buffer_lbl = new wxStaticText(parent, wxID_ANY, _("Buffer:")); wxSpinCtrl* const padbuf_spin = new wxSpinCtrl(parent, wxID_ANY, std::to_string(INITIAL_PAD_BUFFER_SIZE), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 200, INITIAL_PAD_BUFFER_SIZE); padbuf_spin->Bind(wxEVT_SPINCTRL, &NetPlayDialog::OnAdjustBuffer, this); padbuf_spin->SetMinSize(WxUtils::GetTextWidgetMinSize(padbuf_spin)); m_memcard_write = new wxCheckBox(parent, wxID_ANY, _("Write to memory cards/SD")); bottom_szr->Add(m_start_btn, 0, wxALIGN_CENTER_VERTICAL); bottom_szr->Add(buffer_lbl, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, space5); bottom_szr->Add(padbuf_spin, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, space5); bottom_szr->Add(m_memcard_write, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, space5); bottom_szr->AddSpacer(space5); } m_record_chkbox = new wxCheckBox(parent, wxID_ANY, _("Record inputs")); wxButton* quit_btn = new wxButton(parent, wxID_ANY, _("Quit Netplay")); quit_btn->Bind(wxEVT_BUTTON, &NetPlayDialog::OnQuit, this); bottom_szr->Add(m_record_chkbox, 0, wxALIGN_CENTER_VERTICAL); bottom_szr->AddStretchSpacer(); bottom_szr->Add(quit_btn); return bottom_szr; } NetPlayDialog::~NetPlayDialog() { IniFile inifile; const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); inifile.Load(dolphin_ini); IniFile::Section& netplay_config = *inifile.GetOrCreateSection("NetPlay"); netplay_config.Set("NetWindowPosX", GetPosition().x); netplay_config.Set("NetWindowPosY", GetPosition().y); netplay_config.Set("NetWindowWidth", GetSize().GetWidth()); netplay_config.Set("NetWindowHeight", GetSize().GetHeight()); inifile.Save(dolphin_ini); if (netplay_client) { delete netplay_client; netplay_client = nullptr; } if (netplay_server) { delete netplay_server; netplay_server = nullptr; } npd = nullptr; } void NetPlayDialog::OnChat(wxCommandEvent&) { std::string text = WxStrToStr(m_chat_msg_text->GetValue()); if (!text.empty()) { netplay_client->SendChatMessage(text); m_chat_msg_text->Clear(); AddChatMessage(ChatMessageType::UserOut, text); } } void NetPlayDialog::GetNetSettings(NetSettings& 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_memcard_write->GetValue(); 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]; } std::string NetPlayDialog::FindGame(const std::string& target_game) { // find path for selected game, sloppy.. for (u32 i = 0; auto game = m_game_list->GetISO(i); ++i) if (target_game == game->GetUniqueIdentifier()) return game->GetFileName(); return ""; } std::string NetPlayDialog::FindCurrentGame() { return FindGame(m_selected_game); } void NetPlayDialog::OnStart(wxCommandEvent&) { bool should_start = true; if (!netplay_client->DoAllPlayersHaveGame()) { should_start = wxMessageBox(_("Not all players have the game. Do you really want to start?"), _("Warning"), wxYES_NO) == wxYES; } if (!should_start) return; NetSettings settings; GetNetSettings(settings); netplay_server->SetNetSettings(settings); netplay_server->StartGame(); } void NetPlayDialog::BootGame(const std::string& filename) { main_frame->BootGame(filename); } void NetPlayDialog::StopGame() { main_frame->DoStop(); } // NetPlayUI methods called from ---NETPLAY--- thread void NetPlayDialog::Update() { wxThreadEvent evt(wxEVT_THREAD, 1); GetEventHandler()->AddPendingEvent(evt); } void NetPlayDialog::AppendChat(const std::string& msg) { chat_msgs.Push(msg); // silly Update(); } void NetPlayDialog::OnMsgChangeGame(const std::string& filename) { wxThreadEvent* evt = new wxThreadEvent(wxEVT_THREAD, NP_GUI_EVT_CHANGE_GAME); evt->SetString(StrToWxStr(filename)); GetEventHandler()->QueueEvent(evt); } void NetPlayDialog::OnMsgStartGame() { wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_START_GAME); GetEventHandler()->AddPendingEvent(evt); if (m_is_hosting) { m_start_btn->Disable(); m_memcard_write->Disable(); m_game_btn->Disable(); m_player_config_btn->Disable(); } m_record_chkbox->Disable(); } void NetPlayDialog::OnMsgStopGame() { wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_STOP_GAME); GetEventHandler()->AddPendingEvent(evt); if (m_is_hosting) { m_start_btn->Enable(); m_memcard_write->Enable(); m_game_btn->Enable(); m_player_config_btn->Enable(); } m_record_chkbox->Enable(); } void NetPlayDialog::OnAdjustBuffer(wxCommandEvent& event) { const int val = ((wxSpinCtrl*)event.GetEventObject())->GetValue(); netplay_server->AdjustPadBufferSize(val); } 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&) { Destroy(); } // update gui void NetPlayDialog::OnThread(wxThreadEvent& event) { if (m_is_hosting && m_host_label && g_TraversalClient) { UpdateHostLabel(); } // player list m_playerids.clear(); std::string tmps; netplay_client->GetPlayerList(tmps, m_playerids); wxString selection; if (m_player_lbox->GetSelection() != wxNOT_FOUND) selection = m_player_lbox->GetString(m_player_lbox->GetSelection()); m_player_lbox->Clear(); std::istringstream ss(tmps); while (std::getline(ss, tmps)) m_player_lbox->Append(StrToWxStr(tmps)); // remove ping from selection string, in case it has changed selection.erase(selection.rfind('|') + 1); if (!selection.empty()) { for (unsigned int i = 0; i < m_player_lbox->GetCount(); ++i) { if (selection == m_player_lbox->GetString(i).substr(0, selection.length())) { m_player_lbox->SetSelection(i); break; } } } // flash window in taskbar when someone joins if window isn't active static u8 numPlayers = 1; if (netplay_server != nullptr && numPlayers < m_playerids.size() && !HasFocus()) { RequestUserAttention(); } numPlayers = m_playerids.size(); switch (event.GetId()) { case NP_GUI_EVT_CHANGE_GAME: // update selected game :/ { m_selected_game = WxStrToStr(event.GetString()); wxString button_label = event.GetString(); m_game_btn->SetLabel(button_label.Prepend(_(" Game : "))); } break; case NP_GUI_EVT_START_GAME: // client start game :/ { netplay_client->StartGame(FindCurrentGame()); std::string msg = "Starting game"; AddChatMessage(ChatMessageType::Info, msg); } break; case NP_GUI_EVT_STOP_GAME: // client stop game { std::string msg = "Stopping game"; AddChatMessage(ChatMessageType::Info, msg); } break; case NP_GUI_EVT_DISPLAY_MD5_DIALOG: { m_MD5_dialog = new MD5Dialog(this, netplay_server, netplay_client->GetPlayers(), event.GetString().ToStdString()); m_MD5_dialog->Show(); } break; case NP_GUI_EVT_MD5_PROGRESS: { if (m_MD5_dialog == nullptr || m_MD5_dialog->IsBeingDeleted()) break; std::pair payload = event.GetPayload>(); m_MD5_dialog->SetProgress(payload.first, payload.second); } break; case NP_GUI_EVT_MD5_RESULT: { if (m_MD5_dialog == nullptr || m_MD5_dialog->IsBeingDeleted()) break; std::pair payload = event.GetPayload>(); 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 while (chat_msgs.Size()) { std::string s; chat_msgs.Pop(s); AddChatMessage(ChatMessageType::UserIn, s); if (g_ActiveConfig.bShowNetPlayMessages) { OSD::AddMessage(s, OSD::Duration::NORMAL, OSD::Color::GREEN); } } } void NetPlayDialog::OnChangeGame(wxCommandEvent&) { ChangeGameDialog change_game_dialog(this, m_game_list); change_game_dialog.ShowModal(); wxString game_name = change_game_dialog.GetChosenGameName(); if (game_name.empty()) return; m_selected_game = WxStrToStr(game_name); netplay_server->ChangeGame(m_selected_game); m_game_btn->SetLabel(game_name.Prepend(_(" Game : "))); } void NetPlayDialog::OnMD5ComputeRequested(wxCommandEvent&) { MD5Target selection = static_cast(m_MD5_choice->GetSelection()); std::string file_identifier; ChangeGameDialog change_game_dialog(this, m_game_list); m_MD5_choice->SetSelection(0); switch (selection) { case MD5Target::CurrentGame: file_identifier = m_selected_game; break; case MD5Target::OtherGame: change_game_dialog.ShowModal(); file_identifier = WxStrToStr(change_game_dialog.GetChosenGameName()); if (file_identifier.empty()) return; break; case MD5Target::SdCard: file_identifier = WII_SDCARD; break; default: return; } netplay_server->ComputeMD5(file_identifier); } void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier) { wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_DISPLAY_MD5_DIALOG); evt.SetString(file_identifier); GetEventHandler()->AddPendingEvent(evt); } void NetPlayDialog::SetMD5Progress(int pid, int progress) { wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_MD5_PROGRESS); evt.SetPayload(std::pair(pid, progress)); GetEventHandler()->AddPendingEvent(evt); } void NetPlayDialog::SetMD5Result(int pid, const std::string& result) { wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_MD5_RESULT); evt.SetPayload(std::pair(pid, result)); GetEventHandler()->AddPendingEvent(evt); } void NetPlayDialog::AbortMD5() { if (m_MD5_dialog != nullptr && !m_MD5_dialog->IsBeingDeleted()) m_MD5_dialog->Destroy(); } void NetPlayDialog::OnAssignPads(wxCommandEvent&) { PadMapDialog pmd(this, netplay_server, netplay_client); pmd.ShowModal(); netplay_server->SetPadMapping(pmd.GetModifiedPadMappings()); netplay_server->SetWiimoteMapping(pmd.GetModifiedWiimoteMappings()); } void NetPlayDialog::OnKick(wxCommandEvent&) { wxString selection = m_player_lbox->GetStringSelection(); unsigned long player = 0; selection.substr(selection.rfind('[') + 1, selection.rfind(']')).ToULong(&player); netplay_server->KickPlayer((u8)player); m_player_lbox->SetSelection(wxNOT_FOUND); wxCommandEvent event; OnPlayerSelect(event); } void NetPlayDialog::OnPlayerSelect(wxCommandEvent&) { m_kick_btn->Enable(m_player_lbox->GetSelection() > 0); } bool NetPlayDialog::IsRecording() { return m_record_chkbox->GetValue(); } void NetPlayDialog::OnCopyIP(wxCommandEvent&) { if (m_host_copy_btn_is_retry) { g_TraversalClient->ReconnectToServer(); Update(); } else { if (wxTheClipboard->Open()) { wxTheClipboard->SetData(new wxTextDataObject(m_host_label->GetLabel())); wxTheClipboard->Close(); } } } void NetPlayDialog::OnChoice(wxCommandEvent& event) { UpdateHostLabel(); } void NetPlayDialog::UpdateHostLabel() { wxString label = _(" (internal IP)"); auto DeLabel = [=](wxString str) { if (str == _("Localhost")) return std::string("!local!"); return WxStrToStr(str.Left(str.Len() - label.Len())); }; auto EnLabel = [=](std::string str) -> wxString { if (str == "!local!") return _("Localhost"); return StrToWxStr(str) + label; }; int sel = m_host_type_choice->GetSelection(); if (sel == 0) { // the traversal ID switch (g_TraversalClient->m_State) { case TraversalClient::Connecting: m_host_label->SetForegroundColour(*wxLIGHT_GREY); m_host_label->SetLabel("..."); m_host_copy_btn->SetLabel(_("Copy")); m_host_copy_btn->Disable(); break; case TraversalClient::Connected: m_host_label->SetForegroundColour(*wxBLACK); m_host_label->SetLabel( wxString(g_TraversalClient->m_HostId.data(), g_TraversalClient->m_HostId.size())); m_host_copy_btn->SetLabel(_("Copy")); m_host_copy_btn->Enable(); m_host_copy_btn_is_retry = false; break; case TraversalClient::Failure: m_host_label->SetForegroundColour(*wxBLACK); m_host_label->SetLabel("..."); m_host_copy_btn->SetLabel(_("Retry")); m_host_copy_btn->Enable(); m_host_copy_btn_is_retry = true; break; } } else if (sel != wxNOT_FOUND) // wxNOT_FOUND shouldn't generally happen { m_host_label->SetForegroundColour(*wxBLACK); m_host_label->SetLabel( netplay_server->GetInterfaceHost(DeLabel(m_host_type_choice->GetString(sel)))); m_host_copy_btn->SetLabel(_("Copy")); m_host_copy_btn->Enable(); m_host_copy_btn_is_retry = false; } auto set = netplay_server->GetInterfaceSet(); for (const std::string& iface : set) { wxString wxIface = EnLabel(iface); if (m_host_type_choice->FindString(wxIface) == wxNOT_FOUND) m_host_type_choice->Append(wxIface); } for (unsigned i = 1, count = m_host_type_choice->GetCount(); i != count; i++) { if (set.find(DeLabel(m_host_type_choice->GetString(i))) == set.end()) { m_host_type_choice->Delete(i); i--; count--; } } } 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")); }