diff --git a/Source/Core/Core/Config/NetplaySettings.cpp b/Source/Core/Core/Config/NetplaySettings.cpp index 6abedb7f82..1f01452bcb 100644 --- a/Source/Core/Core/Config/NetplaySettings.cpp +++ b/Source/Core/Core/Config/NetplaySettings.cpp @@ -55,5 +55,8 @@ const ConfigInfo NETPLAY_HOST_INPUT_AUTHORITY{{System::Main, "NetPlay", "H false}; const ConfigInfo NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"}, false}; +const ConfigInfo NETPLAY_GOLF_MODE{{System::Main, "NetPlay", "GolfMode"}, false}; +const ConfigInfo NETPLAY_GOLF_MODE_OVERLAY{{System::Main, "NetPlay", "GolfModeOverlay"}, + true}; } // namespace Config diff --git a/Source/Core/Core/Config/NetplaySettings.h b/Source/Core/Core/Config/NetplaySettings.h index a7d9cf667d..adbac875e0 100644 --- a/Source/Core/Core/Config/NetplaySettings.h +++ b/Source/Core/Core/Config/NetplaySettings.h @@ -45,5 +45,7 @@ extern const ConfigInfo NETPLAY_REDUCE_POLLING_RATE; extern const ConfigInfo NETPLAY_STRICT_SETTINGS_SYNC; extern const ConfigInfo NETPLAY_HOST_INPUT_AUTHORITY; extern const ConfigInfo NETPLAY_SYNC_ALL_WII_SAVES; +extern const ConfigInfo NETPLAY_GOLF_MODE; +extern const ConfigInfo NETPLAY_GOLF_MODE_OVERLAY; } // namespace Config diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index 2a161bdf45..8f32b80109 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -20,7 +20,7 @@ #include "InputCommon/GCPadStatus.h" // clang-format off -constexpr std::array s_hotkey_labels{{ +constexpr std::array s_hotkey_labels{{ _trans("Open"), _trans("Change Disc"), _trans("Eject Disc"), @@ -32,6 +32,7 @@ constexpr std::array s_hotkey_labels{{ _trans("Take Screenshot"), _trans("Exit"), _trans("Activate NetPlay Chat"), + _trans("Control NetPlay Golf Mode"), _trans("Volume Down"), _trans("Volume Up"), @@ -275,7 +276,7 @@ struct HotkeyGroupInfo }; constexpr std::array s_groups_info = { - {{_trans("General"), HK_OPEN, HK_ACTIVATE_CHAT}, + {{_trans("General"), HK_OPEN, HK_REQUEST_GOLF_CONTROL}, {_trans("Volume"), HK_VOLUME_DOWN, HK_VOLUME_TOGGLE_MUTE}, {_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE}, {_trans("Frame Advance"), HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_RESET_SPEED}, diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index 3dbb88ce9b..a1dd55b03c 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -15,7 +15,7 @@ namespace ControllerEmu { class ControllerEmu; class Buttons; -} +} // namespace ControllerEmu enum Hotkey { @@ -30,6 +30,7 @@ enum Hotkey HK_SCREENSHOT, HK_EXIT, HK_ACTIVATE_CHAT, + HK_REQUEST_GOLF_CONTROL, HK_VOLUME_DOWN, HK_VOLUME_UP, @@ -236,4 +237,4 @@ void GetStatus(); bool IsEnabled(); void Enable(bool enable_toggle); bool IsPressed(int Id, bool held); -} +} // namespace HotkeyManagerEmu diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index c90aba3959..a2e6dcc562 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -442,6 +442,30 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_PAD_HOST_DATA: + { + while (!packet.endOfPacket()) + { + PadIndex map; + packet >> map; + + GCPadStatus pad; + packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> + pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + + // Trusting server for good map value (>=0 && <4) + // write to last status + m_last_pad_status[map] = pad; + + if (!m_first_pad_status_received[map]) + { + m_first_pad_status_received[map] = true; + m_first_pad_status_received_event.Set(); + } + } + } + break; + case NP_MSG_WIIMOTE_DATA: { PadIndex map; @@ -471,15 +495,6 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; - case NP_MSG_PAD_FIRST_RECEIVED: - { - PadIndex map; - packet >> map; - packet >> m_first_pad_status_received[map]; - m_first_pad_status_received_event.Set(); - } - break; - case NP_MSG_HOST_INPUT_AUTHORITY: { packet >> m_host_input_authority; @@ -487,6 +502,43 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_GOLF_SWITCH: + { + PlayerId pid; + packet >> pid; + + const PlayerId previous_golfer = m_current_golfer; + m_current_golfer = pid; + m_dialog->OnGolferChanged(m_local_player->pid == pid, pid != 0 ? m_players[pid].name : ""); + + if (m_local_player->pid == previous_golfer) + { + sf::Packet spac; + spac << static_cast(NP_MSG_GOLF_RELEASE); + Send(spac); + } + else if (m_local_player->pid == pid) + { + sf::Packet spac; + spac << static_cast(NP_MSG_GOLF_ACQUIRE); + Send(spac); + + // Pads are already calibrated so we can just ignore this + m_first_pad_status_received.fill(true); + + m_wait_on_input = false; + m_wait_on_input_event.Set(); + } + } + break; + + case NP_MSG_GOLF_PREPARE: + { + m_wait_on_input_received = true; + m_wait_on_input = true; + } + break; + case NP_MSG_CHANGE_GAME: { { @@ -622,6 +674,8 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) for (int& extension : m_net_settings.m_WiimoteExtension) packet >> extension; + packet >> m_net_settings.m_GolfMode; + m_net_settings.m_IsHosting = m_local_player->IsHost(); m_net_settings.m_HostInputAuthority = m_host_input_authority; } @@ -1375,6 +1429,8 @@ bool NetPlayClient::StartGame(const std::string& path) } m_timebase_frame = 0; + m_current_golfer = 1; + m_wait_on_input = false; m_is_running.Set(); NetPlay_Enable(this); @@ -1686,6 +1742,27 @@ bool NetPlayClient::GetNetPads(const int pad_nb, const bool batching, GCPadStatu // specific pad arbitrarily. In this case, we poll just that pad // and send it. + // When here when told to so we don't deadlock in certain situations + while (m_wait_on_input) + { + if (!m_is_running.IsSet()) + { + return false; + } + + if (m_wait_on_input_received) + { + // Tell the server we've acknowledged the message + sf::Packet spac; + spac << static_cast(NP_MSG_GOLF_PREPARE); + Send(spac); + + m_wait_on_input_received = false; + } + + m_wait_on_input_event.Wait(); + } + if (IsFirstInGamePad(pad_nb) && batching) { sf::Packet packet; @@ -1720,22 +1797,30 @@ bool NetPlayClient::GetNetPads(const int pad_nb, const bool batching, GCPadStatu SendPadHostPoll(pad_nb); } - if (m_host_input_authority && !m_local_player->IsHost()) + if (m_host_input_authority) { - // CoreTiming acts funny and causes what looks like frame skip if - // we toggle the emulation speed too quickly, so to prevent this - // we wait until the buffer has been over for at least 1 second. - - const bool buffer_over_target = m_pad_buffer[pad_nb].Size() > m_target_buffer_size + 1; - if (!buffer_over_target) - m_buffer_under_target_last = std::chrono::steady_clock::now(); - - std::chrono::duration time_diff = - std::chrono::steady_clock::now() - m_buffer_under_target_last; - if (time_diff.count() >= 1.0 || !buffer_over_target) + if (m_local_player->pid != m_current_golfer) { - // run fast if the buffer is overfilled, otherwise run normal speed - SConfig::GetInstance().m_EmulationSpeed = buffer_over_target ? 0.0f : 1.0f; + // CoreTiming acts funny and causes what looks like frame skip if + // we toggle the emulation speed too quickly, so to prevent this + // we wait until the buffer has been over for at least 1 second. + + const bool buffer_over_target = m_pad_buffer[pad_nb].Size() > m_target_buffer_size + 1; + if (!buffer_over_target) + m_buffer_under_target_last = std::chrono::steady_clock::now(); + + std::chrono::duration time_diff = + std::chrono::steady_clock::now() - m_buffer_under_target_last; + if (time_diff.count() >= 1.0 || !buffer_over_target) + { + // run fast if the buffer is overfilled, otherwise run normal speed + SConfig::GetInstance().m_EmulationSpeed = buffer_over_target ? 0.0f : 1.0f; + } + } + else + { + // Set normal speed when we're the host, otherwise it can get stuck at unlimited + SConfig::GetInstance().m_EmulationSpeed = 1.0f; } } @@ -1865,9 +1950,18 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet) if (m_host_input_authority) { - // add to packet - AddPadStateToPacket(ingame_pad, pad_status, packet); - data_added = true; + if (m_local_player->pid != m_current_golfer) + { + // add to packet + AddPadStateToPacket(ingame_pad, pad_status, packet); + data_added = true; + } + else + { + // set locally + m_last_pad_status[ingame_pad] = pad_status; + m_first_pad_status_received[ingame_pad] = true; + } } else { @@ -1889,9 +1983,12 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet) void NetPlayClient::SendPadHostPoll(const PadIndex pad_num) { - if (!m_local_player->IsHost()) + if (m_local_player->pid != m_current_golfer) return; + sf::Packet packet; + packet << static_cast(NP_MSG_PAD_HOST_DATA); + if (pad_num < 0) { for (size_t i = 0; i < m_pad_map.size(); i++) @@ -1907,6 +2004,16 @@ void NetPlayClient::SendPadHostPoll(const PadIndex pad_num) m_first_pad_status_received_event.Wait(); } } + + for (size_t i = 0; i < m_pad_map.size(); i++) + { + if (m_pad_map[i] == 0 || m_pad_buffer[i].Size() > 0) + continue; + + const GCPadStatus& pad_status = m_last_pad_status[i]; + m_pad_buffer[i].Push(pad_status); + AddPadStateToPacket(static_cast(i), pad_status, packet); + } } else if (m_pad_map[pad_num] != 0) { @@ -1917,11 +2024,15 @@ void NetPlayClient::SendPadHostPoll(const PadIndex pad_num) m_first_pad_status_received_event.Wait(); } + + if (m_pad_buffer[pad_num].Size() == 0) + { + const GCPadStatus& pad_status = m_last_pad_status[pad_num]; + m_pad_buffer[pad_num].Push(pad_status); + AddPadStateToPacket(pad_num, pad_status, packet); + } } - sf::Packet packet; - packet << static_cast(NP_MSG_PAD_HOST_POLL); - packet << pad_num; SendAsync(std::move(packet)); } @@ -1934,6 +2045,7 @@ bool NetPlayClient::StopGame() m_gc_pad_event.Set(); m_wii_pad_event.Set(); m_first_pad_status_received_event.Set(); + m_wait_on_input_event.Set(); NetPlay_Disable(); @@ -1957,6 +2069,7 @@ void NetPlayClient::Stop() m_gc_pad_event.Set(); m_wii_pad_event.Set(); m_first_pad_status_received_event.Set(); + m_wait_on_input_event.Set(); // Tell the server to stop if we have a pad mapped in game. if (LocalPlayerHasControllerMapped()) @@ -1979,15 +2092,35 @@ void NetPlayClient::SendPowerButtonEvent() SendAsync(std::move(packet)); } +void NetPlayClient::RequestGolfControl(const PlayerId pid) +{ + if (!m_host_input_authority || !m_net_settings.m_GolfMode) + return; + + sf::Packet packet; + packet << static_cast(NP_MSG_GOLF_REQUEST); + packet << pid; + SendAsync(std::move(packet)); +} + +void NetPlayClient::RequestGolfControl() +{ + RequestGolfControl(m_local_player->pid); +} + +// called from ---GUI--- thread +std::string NetPlayClient::GetCurrentGolfer() +{ + std::lock_guard lkp(m_crit.players); + if (m_players.count(m_current_golfer)) + return m_players[m_current_golfer].name; + return ""; +} + // called from ---GUI--- thread bool NetPlayClient::LocalPlayerHasControllerMapped() const { - const auto mapping_matches_player_id = [this](const PlayerId& mapping) { - return mapping == m_local_player->pid; - }; - - return std::any_of(m_pad_map.begin(), m_pad_map.end(), mapping_matches_player_id) || - std::any_of(m_wiimote_map.begin(), m_wiimote_map.end(), mapping_matches_player_id); + return PlayerHasControllerMapped(m_local_player->pid); } bool NetPlayClient::IsFirstInGamePad(int ingame_pad) const @@ -2040,6 +2173,19 @@ int NetPlayClient::LocalPadToInGamePad(int local_pad) const return ingame_pad; } +bool NetPlayClient::PlayerHasControllerMapped(const PlayerId pid) const +{ + const auto mapping_matches_player_id = [pid](const PlayerId& mapping) { return mapping == pid; }; + + return std::any_of(m_pad_map.begin(), m_pad_map.end(), mapping_matches_player_id) || + std::any_of(m_wiimote_map.begin(), m_wiimote_map.end(), mapping_matches_player_id); +} + +bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const +{ + return pid == m_local_player->pid; +} + void NetPlayClient::SendTimeBase() { std::lock_guard lk(crit_netplay_client); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index e7b3b20363..9afa624dd4 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -54,6 +54,7 @@ public: virtual void OnTraversalError(TraversalClient::FailureReason error) = 0; virtual void OnTraversalStateChanged(TraversalClient::State state) = 0; virtual void OnSaveDataSyncFailure() = 0; + virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 0; virtual bool IsRecording() = 0; virtual std::string FindGame(const std::string& game) = 0; @@ -111,6 +112,9 @@ public: void SendChatMessage(const std::string& msg); void RequestStopGame(); void SendPowerButtonEvent(); + void RequestGolfControl(PlayerId pid); + void RequestGolfControl(); + std::string GetCurrentGolfer(); // Send and receive pads values bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode); @@ -128,6 +132,10 @@ public: int InGamePadToLocalPad(int ingame_pad) const; int LocalPadToInGamePad(int localPad) const; + bool PlayerHasControllerMapped(PlayerId pid) const; + bool LocalPlayerHasControllerMapped() const; + bool IsLocalPlayer(PlayerId pid) const; + static void SendTimeBase(); bool DoAllPlayersHaveGame(); @@ -158,6 +166,7 @@ protected: std::array, 4> m_pad_buffer; std::array, 4> m_wiimote_buffer; + std::array m_last_pad_status{}; std::array m_first_pad_status_received{}; std::chrono::time_point m_buffer_under_target_last; @@ -178,6 +187,12 @@ protected: // speeding up the game to drain the buffer. unsigned int m_target_buffer_size = 20; bool m_host_input_authority = false; + PlayerId m_current_golfer = 1; + + // This bool will stall the client at the start of GetNetPads, used for switching input control + // without deadlocking. Use the correspondingly named Event to wake it up. + bool m_wait_on_input; + bool m_wait_on_input_received; Player* m_local_player = nullptr; @@ -199,8 +214,6 @@ private: Failure }; - bool LocalPlayerHasControllerMapped() const; - void SendStartGamePacket(); void SendStopGamePacket(); @@ -238,6 +251,7 @@ private: Common::Event m_gc_pad_event; Common::Event m_wii_pad_event; Common::Event m_first_pad_status_received_event; + Common::Event m_wait_on_input_event; u8 m_sync_save_data_count = 0; u8 m_sync_save_data_success_count = 0; u16 m_sync_gecko_codes_count = 0; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index 5eaedcd840..a517cdd9ba 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -83,6 +83,9 @@ struct NetSettings std::string m_SaveDataRegion; bool m_SyncAllWiiSaves; std::array m_WiimoteExtension; + bool m_GolfMode; + + // These aren't sent over the network directly bool m_IsHosting; bool m_HostInputAuthority; }; @@ -123,12 +126,17 @@ enum NP_MSG_PAD_DATA = 0x60, NP_MSG_PAD_MAPPING = 0x61, NP_MSG_PAD_BUFFER = 0x62, - NP_MSG_PAD_HOST_POLL = 0x63, - NP_MSG_PAD_FIRST_RECEIVED = 0x64, + NP_MSG_PAD_HOST_DATA = 0x63, NP_MSG_WIIMOTE_DATA = 0x70, NP_MSG_WIIMOTE_MAPPING = 0x71, + NP_MSG_GOLF_REQUEST = 0x90, + NP_MSG_GOLF_SWITCH = 0x91, + NP_MSG_GOLF_ACQUIRE = 0x92, + NP_MSG_GOLF_RELEASE = 0x93, + NP_MSG_GOLF_PREPARE = 0x94, + NP_MSG_START_GAME = 0xA0, NP_MSG_CHANGE_GAME = 0xA1, NP_MSG_STOP_GAME = 0xA2, diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 166ce881bf..017fc75588 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -527,12 +527,16 @@ void NetPlayServer::AdjustPadBufferSize(unsigned int size) m_target_buffer_size = size; - // tell clients to change buffer size - sf::Packet spac; - spac << static_cast(NP_MSG_PAD_BUFFER); - spac << static_cast(m_target_buffer_size); + // not needed on clients with host input authority + if (!m_host_input_authority) + { + // tell clients to change buffer size + sf::Packet spac; + spac << static_cast(NP_MSG_PAD_BUFFER); + spac << static_cast(m_target_buffer_size); - SendAsyncToClients(std::move(spac)); + SendAsyncToClients(std::move(spac)); + } } void NetPlayServer::SetHostInputAuthority(const bool enable) @@ -652,7 +656,7 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) break; sf::Packet spac; - spac << static_cast(NP_MSG_PAD_DATA); + spac << static_cast(m_host_input_authority ? NP_MSG_PAD_HOST_DATA : NP_MSG_PAD_DATA); while (!packet.endOfPacket()) { @@ -670,59 +674,48 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; - if (m_host_input_authority) - { - m_last_pad_status[map] = pad; - - if (!m_first_pad_status_received[map]) - { - m_first_pad_status_received[map] = true; - SendFirstReceivedToHost(map, true); - } - } - else - { - spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY - << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight - << pad.isConnected; - } - } - - if (!m_host_input_authority) - SendToClients(spac, player.pid); - } - break; - - case NP_MSG_PAD_HOST_POLL: - { - PadIndex pad_num; - packet >> pad_num; - - sf::Packet spac; - spac << static_cast(NP_MSG_PAD_DATA); - - if (pad_num < 0) - { - for (size_t i = 0; i < m_pad_map.size(); i++) - { - if (m_pad_map[i] == 0) - continue; - - const GCPadStatus& pad = m_last_pad_status[i]; - spac << static_cast(i) << pad.button << pad.analogA << pad.analogB << pad.stickX - << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight - << pad.isConnected; - } - } - else if (m_pad_map.at(pad_num) != 0) - { - const GCPadStatus& pad = m_last_pad_status[pad_num]; - spac << pad_num << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY + spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } - SendToClients(spac); + if (m_host_input_authority) + { + // Prevent crash before game stop if the golfer disconnects + if (m_current_golfer != 0 && m_players.find(m_current_golfer) != m_players.end()) + Send(m_players.at(m_current_golfer).socket, spac); + } + else + { + SendToClients(spac, player.pid); + } + } + break; + + case NP_MSG_PAD_HOST_DATA: + { + // Kick player if they're not the golfer. + if (m_current_golfer != 0 && player.pid != m_current_golfer) + return 1; + + sf::Packet spac; + spac << static_cast(NP_MSG_PAD_DATA); + + while (!packet.endOfPacket()) + { + PadIndex map; + packet >> map; + + GCPadStatus pad; + packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> + pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; + + spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY + << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight + << pad.isConnected; + } + + SendToClients(spac, player.pid); } break; @@ -758,6 +751,63 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) } break; + case NP_MSG_GOLF_REQUEST: + { + PlayerId pid; + packet >> pid; + + // Check if player ID is valid and sender isn't a spectator + if (!m_players.count(pid) || !PlayerHasControllerMapped(player.pid)) + break; + + if (m_host_input_authority && m_settings.m_GolfMode && m_pending_golfer == 0 && + m_current_golfer != pid && PlayerHasControllerMapped(pid)) + { + m_pending_golfer = pid; + + sf::Packet spac; + spac << static_cast(NP_MSG_GOLF_PREPARE); + Send(m_players[pid].socket, spac); + } + } + break; + + case NP_MSG_GOLF_RELEASE: + { + if (m_pending_golfer == 0) + break; + + sf::Packet spac; + spac << static_cast(NP_MSG_GOLF_SWITCH); + spac << static_cast(m_pending_golfer); + SendToClients(spac); + } + break; + + case NP_MSG_GOLF_ACQUIRE: + { + if (m_pending_golfer == 0) + break; + + m_current_golfer = m_pending_golfer; + m_pending_golfer = 0; + } + break; + + case NP_MSG_GOLF_PREPARE: + { + if (m_pending_golfer == 0) + break; + + m_current_golfer = 0; + + sf::Packet spac; + spac << static_cast(NP_MSG_GOLF_SWITCH); + spac << static_cast(0); + SendToClients(spac); + } + break; + case NP_MSG_PONG: { const u32 ping = (u32)m_ping_timer.GetTimeElapsed(); @@ -1141,7 +1191,8 @@ bool NetPlayServer::StartGame() if (!m_host_input_authority) AdjustPadBufferSize(m_target_buffer_size); - m_first_pad_status_received.fill(false); + m_current_golfer = 1; + m_pending_golfer = 0; const sf::Uint64 initial_rtc = GetInitialNetPlayRTC(); @@ -1227,6 +1278,8 @@ bool NetPlayServer::StartGame() spac << extension; } + spac << m_settings.m_GolfMode; + SendAsyncToClients(std::move(spac)); m_start_pending = false; @@ -1720,15 +1773,6 @@ bool NetPlayServer::CompressBufferIntoPacket(const std::vector& in_buffer, s return true; } -void NetPlayServer::SendFirstReceivedToHost(const PadIndex map, const bool state) -{ - sf::Packet pac; - pac << static_cast(NP_MSG_PAD_FIRST_RECEIVED); - pac << map; - pac << state; - Send(m_players.at(1).socket, pac); -} - u64 NetPlayServer::GetInitialNetPlayRTC() const { const auto& config = SConfig::GetInstance(); @@ -1771,6 +1815,14 @@ void NetPlayServer::KickPlayer(PlayerId player) } } +bool NetPlayServer::PlayerHasControllerMapped(const PlayerId pid) const +{ + const auto mapping_matches_player_id = [pid](const PlayerId& mapping) { return mapping == pid; }; + + return std::any_of(m_pad_map.begin(), m_pad_map.end(), mapping_matches_player_id) || + std::any_of(m_wiimote_map.begin(), m_wiimote_map.end(), mapping_matches_player_id); +} + u16 NetPlayServer::GetPort() const { return m_server->address.port; diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 70c354a41d..79e63e1556 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -118,7 +118,6 @@ private: void CheckSyncAndStartGame(); bool CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet); bool CompressBufferIntoPacket(const std::vector& in_buffer, sf::Packet& packet); - void SendFirstReceivedToHost(PadIndex map, bool state); u64 GetInitialNetPlayRTC() const; @@ -138,6 +137,8 @@ private: void ChunkedDataThreadFunc(); void ChunkedDataSend(sf::Packet&& packet, PlayerId pid, const TargetMode target_mode); + bool PlayerHasControllerMapped(PlayerId pid) const; + NetSettings m_settings; bool m_is_running = false; @@ -155,15 +156,14 @@ private: bool m_codes_synced = true; bool m_start_pending = false; bool m_host_input_authority = false; + PlayerId m_current_golfer = 1; + PlayerId m_pending_golfer = 0; std::map m_players; std::unordered_map>> m_timebase_by_frame; bool m_desync_detected; - std::array m_last_pad_status{}; - std::array m_first_pad_status_received{}; - struct { std::recursive_mutex game; diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 956de04486..89277a3905 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -200,6 +200,9 @@ void HotkeyScheduler::Run() if (IsHotkey(HK_ACTIVATE_CHAT)) emit ActivateChat(); + if (IsHotkey(HK_REQUEST_GOLF_CONTROL)) + emit RequestGolfControl(); + // Recording if (IsHotkey(HK_START_RECORDING)) emit StartRecording(); diff --git a/Source/Core/DolphinQt/HotkeyScheduler.h b/Source/Core/DolphinQt/HotkeyScheduler.h index ca5aee36ec..f104abb44d 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.h +++ b/Source/Core/DolphinQt/HotkeyScheduler.h @@ -27,6 +27,7 @@ signals: void ExitHotkey(); void ActivateChat(); + void RequestGolfControl(); void FullScreenHotkey(); void StopHotkey(); void ResetHotkey(); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 9814644f78..78ac86323f 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -494,6 +494,8 @@ void MainWindow::ConnectHotkeys() connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close); connect(m_hotkey_scheduler, &HotkeyScheduler::TogglePauseHotkey, this, &MainWindow::TogglePause); connect(m_hotkey_scheduler, &HotkeyScheduler::ActivateChat, this, &MainWindow::OnActivateChat); + connect(m_hotkey_scheduler, &HotkeyScheduler::RequestGolfControl, this, + &MainWindow::OnRequestGolfControl); connect(m_hotkey_scheduler, &HotkeyScheduler::RefreshGameListHotkey, this, &MainWindow::RefreshGameList); connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::RequestStop); @@ -1284,8 +1286,7 @@ bool MainWindow::NetPlayJoin() if (server) { server->SetHostInputAuthority(host_input_authority); - if (!host_input_authority) - server->AdjustPadBufferSize(Config::Get(Config::NETPLAY_BUFFER_SIZE)); + server->AdjustPadBufferSize(Config::Get(Config::NETPLAY_BUFFER_SIZE)); } // Create Client @@ -1605,6 +1606,13 @@ void MainWindow::OnActivateChat() g_netplay_chat_ui->Activate(); } +void MainWindow::OnRequestGolfControl() +{ + auto client = Settings::Instance().GetNetPlayClient(); + if (client) + client->RequestGolfControl(); +} + void MainWindow::ShowTASInput() { for (int i = 0; i < num_gc_controllers; i++) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index edc6fc479b..bf9027960b 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -170,6 +170,7 @@ private: void OnStopRecording(); void OnExportRecording(); void OnActivateChat(); + void OnRequestGolfControl(); void ShowTASInput(); void ChangeDisc(); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 52ba81937a..e9592ca1fb 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -55,6 +55,7 @@ #include "UICommon/UICommon.h" #include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoConfig.h" @@ -85,6 +86,8 @@ NetPlayDialog::NetPlayDialog(QWidget* parent) const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC); const bool host_input_authority = Config::Get(Config::NETPLAY_HOST_INPUT_AUTHORITY); const bool sync_all_wii_saves = Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES); + const bool golf_mode = Config::Get(Config::NETPLAY_GOLF_MODE); + const bool golf_mode_overlay = Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY); m_buffer_size_box->setValue(buffer_size); m_save_sd_action->setChecked(write_save_sdcard_data); @@ -96,6 +99,8 @@ NetPlayDialog::NetPlayDialog(QWidget* parent) m_strict_settings_sync_action->setChecked(strict_settings_sync); m_host_input_authority_action->setChecked(host_input_authority); m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves); + m_golf_mode_action->setChecked(golf_mode); + m_golf_mode_overlay_action->setChecked(golf_mode_overlay); ConnectWidgets(); @@ -144,10 +149,14 @@ void NetPlayDialog::CreateMainLayout() m_reduce_polling_rate_action->setCheckable(true); m_host_input_authority_action = m_network_menu->addAction(tr("Host Input Authority")); m_host_input_authority_action->setCheckable(true); + m_golf_mode_action = m_network_menu->addAction(tr("Golf Mode")); + m_golf_mode_action->setCheckable(true); m_other_menu = m_menu_bar->addMenu(tr("Other")); m_record_input_action = m_other_menu->addAction(tr("Record Inputs")); m_record_input_action->setCheckable(true); + m_golf_mode_overlay_action = m_other_menu->addAction(tr("Show Golf Mode Overlay")); + m_golf_mode_overlay_action->setCheckable(true); m_game_button->setDefault(false); m_game_button->setAutoDefault(false); @@ -300,16 +309,18 @@ void NetPlayDialog::ConnectWidgets() auto client = Settings::Instance().GetNetPlayClient(); auto server = Settings::Instance().GetNetPlayServer(); - if (server) + if (server && !m_host_input_authority) server->AdjustPadBufferSize(value); else client->AdjustPadBufferSize(value); }); - connect(m_host_input_authority_action, &QAction::toggled, [](bool checked) { + connect(m_host_input_authority_action, &QAction::toggled, this, [=](bool checked) { auto server = Settings::Instance().GetNetPlayServer(); if (server) server->SetHostInputAuthority(checked); + + m_golf_mode_action->setEnabled(checked); }); connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart); @@ -355,6 +366,8 @@ void NetPlayDialog::ConnectWidgets() connect(m_strict_settings_sync_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_host_input_authority_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_sync_all_wii_saves_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); + connect(m_golf_mode_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); + connect(m_golf_mode_overlay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); } void NetPlayDialog::SendMessage(const std::string& msg) @@ -482,6 +495,7 @@ void NetPlayDialog::OnStart() settings.m_SyncCodes = m_sync_codes_action->isChecked(); settings.m_SyncAllWiiSaves = m_sync_all_wii_saves_action->isChecked() && m_sync_save_data_action->isChecked(); + settings.m_GolfMode = m_golf_mode_action->isChecked(); // Unload GameINI to restore things to normal Config::RemoveLayer(Config::LayerType::GlobalGame); @@ -539,8 +553,6 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_game_button->setEnabled(is_hosting); m_kick_button->setEnabled(false); - m_buffer_label->setText(is_hosting ? tr("Buffer:") : tr("Max Buffer:")); - QDialog::show(); UpdateGUI(); } @@ -817,6 +829,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled) m_strict_settings_sync_action->setEnabled(enabled); m_host_input_authority_action->setEnabled(enabled); m_sync_all_wii_saves_action->setEnabled(enabled && m_sync_save_data_action->isChecked()); + m_golf_mode_action->setEnabled(enabled && m_host_input_authority_action->isChecked()); } m_record_input_action->setEnabled(enabled); @@ -829,6 +842,11 @@ void NetPlayDialog::OnMsgStartGame() g_netplay_chat_ui = std::make_unique([this](const std::string& message) { SendMessage(message); }); + if (Settings::Instance().GetNetPlayClient()->GetNetSettings().m_GolfMode) + { + g_netplay_golf_ui = std::make_unique(Settings::Instance().GetNetPlayClient()); + } + QueueOnObject(this, [this] { auto client = Settings::Instance().GetNetPlayClient(); @@ -841,6 +859,7 @@ void NetPlayDialog::OnMsgStartGame() void NetPlayDialog::OnMsgStopGame() { g_netplay_chat_ui.reset(); + g_netplay_golf_ui.reset(); QueueOnObject(this, [this] { UpdateDiscordPresence(); }); } @@ -857,9 +876,8 @@ void NetPlayDialog::OnPadBufferChanged(u32 buffer) const QSignalBlocker blocker(m_buffer_size_box); m_buffer_size_box->setValue(buffer); }); - DisplayMessage(m_host_input_authority && !IsHosting() ? - tr("Max buffer size changed to %1").arg(buffer) : - tr("Buffer size changed to %1").arg(buffer), + DisplayMessage(m_host_input_authority ? tr("Max buffer size changed to %1").arg(buffer) : + tr("Buffer size changed to %1").arg(buffer), "darkcyan"); m_buffer_size = static_cast(buffer); @@ -867,6 +885,10 @@ void NetPlayDialog::OnPadBufferChanged(u32 buffer) void NetPlayDialog::OnHostInputAuthorityChanged(bool enabled) { + m_host_input_authority = enabled; + DisplayMessage(enabled ? tr("Host input authority enabled") : tr("Host input authority disabled"), + ""); + QueueOnObject(this, [this, enabled] { const bool is_hosting = IsHosting(); const bool enable_buffer = is_hosting != enabled; @@ -878,8 +900,9 @@ void NetPlayDialog::OnHostInputAuthorityChanged(bool enabled) m_buffer_size_box->setHidden(false); m_buffer_label->setHidden(false); - QSignalBlocker blocker(m_host_input_authority_action); + const QSignalBlocker blocker(m_host_input_authority_action); m_host_input_authority_action->setChecked(enabled); + m_golf_mode_action->setEnabled(enabled); } else { @@ -887,15 +910,12 @@ void NetPlayDialog::OnHostInputAuthorityChanged(bool enabled) m_buffer_label->setEnabled(true); m_buffer_size_box->setHidden(!enable_buffer); m_buffer_label->setHidden(!enable_buffer); - - if (enabled) - m_buffer_size_box->setValue(Config::Get(Config::NETPLAY_CLIENT_BUFFER_SIZE)); } - }); - DisplayMessage(enabled ? tr("Host input authority enabled") : tr("Host input authority disabled"), - ""); - m_host_input_authority = enabled; + m_buffer_label->setText(enabled ? tr("Max Buffer:") : tr("Buffer:")); + if (enabled) + m_buffer_size_box->setValue(Config::Get(Config::NETPLAY_CLIENT_BUFFER_SIZE)); + }); } void NetPlayDialog::OnDesync(u32 frame, const std::string& player) @@ -958,6 +978,20 @@ void NetPlayDialog::OnSaveDataSyncFailure() QueueOnObject(this, [this] { SetOptionsEnabled(true); }); } +void NetPlayDialog::OnGolferChanged(const bool is_golfer, const std::string& golfer_name) +{ + if (m_host_input_authority) + { + QueueOnObject(this, [this, is_golfer] { + m_buffer_size_box->setEnabled(!is_golfer); + m_buffer_label->setEnabled(!is_golfer); + }); + } + + if (!golfer_name.empty()) + DisplayMessage(tr("%1 is now golfing").arg(QString::fromStdString(golfer_name)), ""); +} + bool NetPlayDialog::IsRecording() { std::optional is_recording = RunOnObject(m_record_input_action, &QAction::isChecked); @@ -1002,14 +1036,10 @@ void NetPlayDialog::SaveSettings() Config::ConfigChangeCallbackGuard config_guard; if (m_host_input_authority) - { - if (!IsHosting()) - Config::SetBase(Config::NETPLAY_CLIENT_BUFFER_SIZE, m_buffer_size_box->value()); - } + Config::SetBase(Config::NETPLAY_CLIENT_BUFFER_SIZE, m_buffer_size_box->value()); else - { Config::SetBase(Config::NETPLAY_BUFFER_SIZE, m_buffer_size_box->value()); - } + Config::SetBase(Config::NETPLAY_WRITE_SAVE_SDCARD_DATA, m_save_sd_action->isChecked()); Config::SetBase(Config::NETPLAY_LOAD_WII_SAVE, m_load_wii_action->isChecked()); Config::SetBase(Config::NETPLAY_SYNC_SAVES, m_sync_save_data_action->isChecked()); @@ -1019,6 +1049,8 @@ void NetPlayDialog::SaveSettings() Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_action->isChecked()); Config::SetBase(Config::NETPLAY_HOST_INPUT_AUTHORITY, m_host_input_authority_action->isChecked()); Config::SetBase(Config::NETPLAY_SYNC_ALL_WII_SAVES, m_sync_all_wii_saves_action->isChecked()); + Config::SetBase(Config::NETPLAY_GOLF_MODE, m_golf_mode_action->isChecked()); + Config::SetBase(Config::NETPLAY_GOLF_MODE_OVERLAY, m_golf_mode_overlay_action->isChecked()); } void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier) diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index 49acc0fd42..558990cb1d 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -58,6 +58,7 @@ public: void OnTraversalError(TraversalClient::FailureReason error) override; void OnTraversalStateChanged(TraversalClient::State state) override; void OnSaveDataSyncFailure() override; + void OnGolferChanged(bool is_golfer, const std::string& golfer_name) override; bool IsRecording() override; std::string FindGame(const std::string& game) override; @@ -131,6 +132,8 @@ private: QAction* m_strict_settings_sync_action; QAction* m_host_input_authority_action; QAction* m_sync_all_wii_saves_action; + QAction* m_golf_mode_action; + QAction* m_golf_mode_overlay_action; QPushButton* m_quit_button; QSplitter* m_splitter; diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 61be8ff8c0..95242b8ceb 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -28,6 +28,7 @@ #include "InputCommon/InputConfig.h" #include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/RenderBase.h" Settings::Settings() @@ -298,6 +299,7 @@ void Settings::ResetNetPlayClient(NetPlay::NetPlayClient* client) m_client.reset(client); g_netplay_chat_ui.reset(); + g_netplay_golf_ui.reset(); } std::shared_ptr Settings::GetNetPlayServer() diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index 459fbcb7aa..dfabf8fede 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(videocommon IndexGenerator.cpp LightingShaderGen.cpp NetPlayChatUI.cpp + NetPlayGolfUI.cpp OnScreenDisplay.cpp OpcodeDecoding.cpp PerfQueryBase.cpp diff --git a/Source/Core/VideoCommon/NetPlayGolfUI.cpp b/Source/Core/VideoCommon/NetPlayGolfUI.cpp new file mode 100644 index 0000000000..3a831b1cf1 --- /dev/null +++ b/Source/Core/VideoCommon/NetPlayGolfUI.cpp @@ -0,0 +1,66 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/StringUtil.h" + +#include "Core/NetPlayClient.h" + +#include "VideoCommon/NetPlayGolfUI.h" + +#include + +constexpr float DEFAULT_WINDOW_WIDTH = 220.0f; +constexpr float DEFAULT_WINDOW_HEIGHT = 45.0f; + +std::unique_ptr g_netplay_golf_ui; + +NetPlayGolfUI::NetPlayGolfUI(std::shared_ptr netplay_client) +{ + m_netplay_client = netplay_client; +} + +void NetPlayGolfUI::Display() +{ + auto client = m_netplay_client.lock(); + if (!client) + return; + + const float scale = ImGui::GetIO().DisplayFramebufferScale.x; + + ImGui::SetNextWindowPos(ImVec2((20.0f + DEFAULT_WINDOW_WIDTH) * scale, 10.0f * scale), + ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints( + ImVec2(DEFAULT_WINDOW_WIDTH * scale, DEFAULT_WINDOW_HEIGHT * scale), + ImGui::GetIO().DisplaySize); + + // TODO: Translate these strings once imgui has multilingual fonts + if (!ImGui::Begin("Golf Mode", nullptr, ImGuiWindowFlags_None)) + { + ImGui::End(); + return; + } + + ImGui::Text("Current Golfer: %s", client->GetCurrentGolfer().c_str()); + + if (client->LocalPlayerHasControllerMapped()) + { + if (ImGui::Button("Take Control")) + { + client->RequestGolfControl(); + } + + for (auto player : client->GetPlayers()) + { + if (client->IsLocalPlayer(player->pid) || !client->PlayerHasControllerMapped(player->pid)) + continue; + + if (ImGui::Button(StringFromFormat("Give Control to %s", player->name.c_str()).c_str())) + { + client->RequestGolfControl(player->pid); + } + } + } + + ImGui::End(); +} diff --git a/Source/Core/VideoCommon/NetPlayGolfUI.h b/Source/Core/VideoCommon/NetPlayGolfUI.h new file mode 100644 index 0000000000..097c6cf600 --- /dev/null +++ b/Source/Core/VideoCommon/NetPlayGolfUI.h @@ -0,0 +1,27 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace NetPlay +{ +class NetPlayClient; +} + +class NetPlayGolfUI +{ +public: + explicit NetPlayGolfUI(std::shared_ptr netplay_client); + ~NetPlayGolfUI() = default; + + void Display(); + +private: + std::weak_ptr m_netplay_client; +}; + +extern std::unique_ptr g_netplay_golf_ui; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 688c7fc43b..09d59e4877 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -37,6 +37,7 @@ #include "Common/Timer.h" #include "Core/Analytics.h" +#include "Core/Config/NetplaySettings.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -58,6 +59,7 @@ #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/ImageWrite.h" #include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/NetPlayGolfUI.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" @@ -532,6 +534,9 @@ void Renderer::DrawDebugText() if (g_ActiveConfig.bShowNetPlayMessages && g_netplay_chat_ui) g_netplay_chat_ui->Display(); + if (Config::Get(Config::NETPLAY_GOLF_MODE_OVERLAY) && g_netplay_golf_ui) + g_netplay_golf_ui->Display(); + if (g_ActiveConfig.bOverlayProjStats) Statistics::DisplayProj(); } diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index d18f7f444e..51f93d0457 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -58,6 +58,7 @@ + @@ -122,6 +123,7 @@ + @@ -192,4 +194,4 @@ - \ No newline at end of file + diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index c1ecc02dbd..ea9e2d3346 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -200,6 +200,9 @@ Util + + Util + @@ -392,8 +395,11 @@ Util + + Util + - \ No newline at end of file +