NetPlay: Implement golf mode

This is an extension of host input authority that allows switching the
host (who has zero latency) on the fly, at the further expense of
everyone else's latency. This is useful for turn-based games where the
latency of players not on their turn doesn't matter.

To become the so-called golfer, the player simply presses a hotkey.
When the host is the golfer, latency is identical to normal host input
authority.
This commit is contained in:
Techjar 2019-04-02 08:08:27 -04:00
parent e2f1da5210
commit 1a12876330
15 changed files with 263 additions and 28 deletions

View File

@ -55,5 +55,6 @@ const ConfigInfo<bool> NETPLAY_HOST_INPUT_AUTHORITY{{System::Main, "NetPlay", "H
false}; false};
const ConfigInfo<bool> NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"}, const ConfigInfo<bool> NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"},
false}; false};
const ConfigInfo<bool> NETPLAY_GOLF_MODE{{System::Main, "NetPlay", "GolfMode"}, false};
} // namespace Config } // namespace Config

View File

@ -45,5 +45,6 @@ extern const ConfigInfo<bool> NETPLAY_REDUCE_POLLING_RATE;
extern const ConfigInfo<bool> NETPLAY_STRICT_SETTINGS_SYNC; extern const ConfigInfo<bool> NETPLAY_STRICT_SETTINGS_SYNC;
extern const ConfigInfo<bool> NETPLAY_HOST_INPUT_AUTHORITY; extern const ConfigInfo<bool> NETPLAY_HOST_INPUT_AUTHORITY;
extern const ConfigInfo<bool> NETPLAY_SYNC_ALL_WII_SAVES; extern const ConfigInfo<bool> NETPLAY_SYNC_ALL_WII_SAVES;
extern const ConfigInfo<bool> NETPLAY_GOLF_MODE;
} // namespace Config } // namespace Config

View File

@ -20,7 +20,7 @@
#include "InputCommon/GCPadStatus.h" #include "InputCommon/GCPadStatus.h"
// clang-format off // clang-format off
constexpr std::array<const char*, 132> s_hotkey_labels{{ constexpr std::array<const char*, 133> s_hotkey_labels{{
_trans("Open"), _trans("Open"),
_trans("Change Disc"), _trans("Change Disc"),
_trans("Eject Disc"), _trans("Eject Disc"),
@ -32,6 +32,7 @@ constexpr std::array<const char*, 132> s_hotkey_labels{{
_trans("Take Screenshot"), _trans("Take Screenshot"),
_trans("Exit"), _trans("Exit"),
_trans("Activate NetPlay Chat"), _trans("Activate NetPlay Chat"),
_trans("Control NetPlay Golf Mode"),
_trans("Volume Down"), _trans("Volume Down"),
_trans("Volume Up"), _trans("Volume Up"),
@ -275,7 +276,7 @@ struct HotkeyGroupInfo
}; };
constexpr std::array<HotkeyGroupInfo, NUM_HOTKEY_GROUPS> s_groups_info = { constexpr std::array<HotkeyGroupInfo, NUM_HOTKEY_GROUPS> 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("Volume"), HK_VOLUME_DOWN, HK_VOLUME_TOGGLE_MUTE},
{_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE}, {_trans("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE},
{_trans("Frame Advance"), HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_RESET_SPEED}, {_trans("Frame Advance"), HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_RESET_SPEED},

View File

@ -15,7 +15,7 @@ namespace ControllerEmu
{ {
class ControllerEmu; class ControllerEmu;
class Buttons; class Buttons;
} } // namespace ControllerEmu
enum Hotkey enum Hotkey
{ {
@ -30,6 +30,7 @@ enum Hotkey
HK_SCREENSHOT, HK_SCREENSHOT,
HK_EXIT, HK_EXIT,
HK_ACTIVATE_CHAT, HK_ACTIVATE_CHAT,
HK_REQUEST_GOLF_CONTROL,
HK_VOLUME_DOWN, HK_VOLUME_DOWN,
HK_VOLUME_UP, HK_VOLUME_UP,
@ -236,4 +237,4 @@ void GetStatus();
bool IsEnabled(); bool IsEnabled();
void Enable(bool enable_toggle); void Enable(bool enable_toggle);
bool IsPressed(int Id, bool held); bool IsPressed(int Id, bool held);
} } // namespace HotkeyManagerEmu

View File

@ -502,6 +502,43 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
} }
break; 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<MessageId>(NP_MSG_GOLF_RELEASE);
Send(spac);
}
else if (m_local_player->pid == pid)
{
sf::Packet spac;
spac << static_cast<MessageId>(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: case NP_MSG_CHANGE_GAME:
{ {
{ {
@ -637,6 +674,8 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
for (int& extension : m_net_settings.m_WiimoteExtension) for (int& extension : m_net_settings.m_WiimoteExtension)
packet >> extension; packet >> extension;
packet >> m_net_settings.m_GolfMode;
m_net_settings.m_IsHosting = m_local_player->IsHost(); m_net_settings.m_IsHosting = m_local_player->IsHost();
m_net_settings.m_HostInputAuthority = m_host_input_authority; m_net_settings.m_HostInputAuthority = m_host_input_authority;
} }
@ -1390,6 +1429,8 @@ bool NetPlayClient::StartGame(const std::string& path)
} }
m_timebase_frame = 0; m_timebase_frame = 0;
m_current_golfer = 1;
m_wait_on_input = false;
m_is_running.Set(); m_is_running.Set();
NetPlay_Enable(this); NetPlay_Enable(this);
@ -1701,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 // specific pad arbitrarily. In this case, we poll just that pad
// and send it. // 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<MessageId>(NP_MSG_GOLF_PREPARE);
Send(spac);
m_wait_on_input_received = false;
}
m_wait_on_input_event.Wait();
}
if (IsFirstInGamePad(pad_nb) && batching) if (IsFirstInGamePad(pad_nb) && batching)
{ {
sf::Packet packet; sf::Packet packet;
@ -1735,22 +1797,30 @@ bool NetPlayClient::GetNetPads(const int pad_nb, const bool batching, GCPadStatu
SendPadHostPoll(pad_nb); 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 if (m_local_player->pid != m_current_golfer)
// 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<double> 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 // CoreTiming acts funny and causes what looks like frame skip if
SConfig::GetInstance().m_EmulationSpeed = buffer_over_target ? 0.0f : 1.0f; // 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<double> 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;
} }
} }
@ -1880,7 +1950,7 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
if (m_host_input_authority) if (m_host_input_authority)
{ {
if (!m_local_player->IsHost()) if (m_local_player->pid != m_current_golfer)
{ {
// add to packet // add to packet
AddPadStateToPacket(ingame_pad, pad_status, packet); AddPadStateToPacket(ingame_pad, pad_status, packet);
@ -1913,7 +1983,7 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
void NetPlayClient::SendPadHostPoll(const PadIndex pad_num) void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
{ {
if (!m_local_player->IsHost()) if (m_local_player->pid != m_current_golfer)
return; return;
sf::Packet packet; sf::Packet packet;
@ -1937,7 +2007,7 @@ void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
for (size_t i = 0; i < m_pad_map.size(); i++) for (size_t i = 0; i < m_pad_map.size(); i++)
{ {
if (m_pad_map[i] == 0) if (m_pad_map[i] == 0 || m_pad_buffer[i].Size() > 0)
continue; continue;
const GCPadStatus& pad_status = m_last_pad_status[i]; const GCPadStatus& pad_status = m_last_pad_status[i];
@ -1955,9 +2025,12 @@ void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
m_first_pad_status_received_event.Wait(); m_first_pad_status_received_event.Wait();
} }
const GCPadStatus& pad_status = m_last_pad_status[pad_num]; if (m_pad_buffer[pad_num].Size() == 0)
m_pad_buffer[pad_num].Push(pad_status); {
AddPadStateToPacket(pad_num, pad_status, packet); const GCPadStatus& pad_status = m_last_pad_status[pad_num];
m_pad_buffer[pad_num].Push(pad_status);
AddPadStateToPacket(pad_num, pad_status, packet);
}
} }
SendAsync(std::move(packet)); SendAsync(std::move(packet));
@ -1972,6 +2045,7 @@ bool NetPlayClient::StopGame()
m_gc_pad_event.Set(); m_gc_pad_event.Set();
m_wii_pad_event.Set(); m_wii_pad_event.Set();
m_first_pad_status_received_event.Set(); m_first_pad_status_received_event.Set();
m_wait_on_input_event.Set();
NetPlay_Disable(); NetPlay_Disable();
@ -1995,6 +2069,7 @@ void NetPlayClient::Stop()
m_gc_pad_event.Set(); m_gc_pad_event.Set();
m_wii_pad_event.Set(); m_wii_pad_event.Set();
m_first_pad_status_received_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. // Tell the server to stop if we have a pad mapped in game.
if (LocalPlayerHasControllerMapped()) if (LocalPlayerHasControllerMapped())
@ -2017,6 +2092,22 @@ void NetPlayClient::SendPowerButtonEvent()
SendAsync(std::move(packet)); 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<MessageId>(NP_MSG_GOLF_REQUEST);
packet << pid;
SendAsync(std::move(packet));
}
void NetPlayClient::RequestGolfControl()
{
RequestGolfControl(m_local_player->pid);
}
// called from ---GUI--- thread // called from ---GUI--- thread
bool NetPlayClient::LocalPlayerHasControllerMapped() const bool NetPlayClient::LocalPlayerHasControllerMapped() const
{ {

View File

@ -54,6 +54,7 @@ public:
virtual void OnTraversalError(TraversalClient::FailureReason error) = 0; virtual void OnTraversalError(TraversalClient::FailureReason error) = 0;
virtual void OnTraversalStateChanged(TraversalClient::State state) = 0; virtual void OnTraversalStateChanged(TraversalClient::State state) = 0;
virtual void OnSaveDataSyncFailure() = 0; virtual void OnSaveDataSyncFailure() = 0;
virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 0;
virtual bool IsRecording() = 0; virtual bool IsRecording() = 0;
virtual std::string FindGame(const std::string& game) = 0; virtual std::string FindGame(const std::string& game) = 0;
@ -111,6 +112,8 @@ public:
void SendChatMessage(const std::string& msg); void SendChatMessage(const std::string& msg);
void RequestStopGame(); void RequestStopGame();
void SendPowerButtonEvent(); void SendPowerButtonEvent();
void RequestGolfControl(PlayerId pid);
void RequestGolfControl();
// Send and receive pads values // Send and receive pads values
bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode); bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode);
@ -179,6 +182,12 @@ protected:
// speeding up the game to drain the buffer. // speeding up the game to drain the buffer.
unsigned int m_target_buffer_size = 20; unsigned int m_target_buffer_size = 20;
bool m_host_input_authority = false; 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; Player* m_local_player = nullptr;
@ -239,6 +248,7 @@ private:
Common::Event m_gc_pad_event; Common::Event m_gc_pad_event;
Common::Event m_wii_pad_event; Common::Event m_wii_pad_event;
Common::Event m_first_pad_status_received_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_count = 0;
u8 m_sync_save_data_success_count = 0; u8 m_sync_save_data_success_count = 0;
u16 m_sync_gecko_codes_count = 0; u16 m_sync_gecko_codes_count = 0;

View File

@ -83,6 +83,9 @@ struct NetSettings
std::string m_SaveDataRegion; std::string m_SaveDataRegion;
bool m_SyncAllWiiSaves; bool m_SyncAllWiiSaves;
std::array<int, 4> m_WiimoteExtension; std::array<int, 4> m_WiimoteExtension;
bool m_GolfMode;
// These aren't sent over the network directly
bool m_IsHosting; bool m_IsHosting;
bool m_HostInputAuthority; bool m_HostInputAuthority;
}; };
@ -128,6 +131,12 @@ enum
NP_MSG_WIIMOTE_DATA = 0x70, NP_MSG_WIIMOTE_DATA = 0x70,
NP_MSG_WIIMOTE_MAPPING = 0x71, 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_START_GAME = 0xA0,
NP_MSG_CHANGE_GAME = 0xA1, NP_MSG_CHANGE_GAME = 0xA1,
NP_MSG_STOP_GAME = 0xA2, NP_MSG_STOP_GAME = 0xA2,

View File

@ -680,16 +680,22 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
} }
if (m_host_input_authority) if (m_host_input_authority)
Send(m_players.at(1).socket, spac); {
// 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 else
{
SendToClients(spac, player.pid); SendToClients(spac, player.pid);
}
} }
break; break;
case NP_MSG_PAD_HOST_DATA: case NP_MSG_PAD_HOST_DATA:
{ {
// Kick player if they're not the host. // Kick player if they're not the golfer.
if (!player.IsHost()) if (m_current_golfer != 0 && player.pid != m_current_golfer)
return 1; return 1;
sf::Packet spac; sf::Packet spac;
@ -745,6 +751,63 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
} }
break; 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<MessageId>(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<MessageId>(NP_MSG_GOLF_SWITCH);
spac << static_cast<PlayerId>(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<MessageId>(NP_MSG_GOLF_SWITCH);
spac << static_cast<PlayerId>(0);
SendToClients(spac);
}
break;
case NP_MSG_PONG: case NP_MSG_PONG:
{ {
const u32 ping = (u32)m_ping_timer.GetTimeElapsed(); const u32 ping = (u32)m_ping_timer.GetTimeElapsed();
@ -1128,6 +1191,9 @@ bool NetPlayServer::StartGame()
if (!m_host_input_authority) if (!m_host_input_authority)
AdjustPadBufferSize(m_target_buffer_size); AdjustPadBufferSize(m_target_buffer_size);
m_current_golfer = 1;
m_pending_golfer = 0;
const sf::Uint64 initial_rtc = GetInitialNetPlayRTC(); const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();
const std::string region = SConfig::GetDirectoryForRegion( const std::string region = SConfig::GetDirectoryForRegion(
@ -1212,6 +1278,8 @@ bool NetPlayServer::StartGame()
spac << extension; spac << extension;
} }
spac << m_settings.m_GolfMode;
SendAsyncToClients(std::move(spac)); SendAsyncToClients(std::move(spac));
m_start_pending = false; m_start_pending = false;
@ -1747,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 u16 NetPlayServer::GetPort() const
{ {
return m_server->address.port; return m_server->address.port;

View File

@ -137,6 +137,8 @@ private:
void ChunkedDataThreadFunc(); void ChunkedDataThreadFunc();
void ChunkedDataSend(sf::Packet&& packet, PlayerId pid, const TargetMode target_mode); void ChunkedDataSend(sf::Packet&& packet, PlayerId pid, const TargetMode target_mode);
bool PlayerHasControllerMapped(PlayerId pid) const;
NetSettings m_settings; NetSettings m_settings;
bool m_is_running = false; bool m_is_running = false;
@ -154,6 +156,8 @@ private:
bool m_codes_synced = true; bool m_codes_synced = true;
bool m_start_pending = false; bool m_start_pending = false;
bool m_host_input_authority = false; bool m_host_input_authority = false;
PlayerId m_current_golfer = 1;
PlayerId m_pending_golfer = 0;
std::map<PlayerId, Client> m_players; std::map<PlayerId, Client> m_players;

View File

@ -200,6 +200,9 @@ void HotkeyScheduler::Run()
if (IsHotkey(HK_ACTIVATE_CHAT)) if (IsHotkey(HK_ACTIVATE_CHAT))
emit ActivateChat(); emit ActivateChat();
if (IsHotkey(HK_REQUEST_GOLF_CONTROL))
emit RequestGolfControl();
// Recording // Recording
if (IsHotkey(HK_START_RECORDING)) if (IsHotkey(HK_START_RECORDING))
emit StartRecording(); emit StartRecording();

View File

@ -27,6 +27,7 @@ signals:
void ExitHotkey(); void ExitHotkey();
void ActivateChat(); void ActivateChat();
void RequestGolfControl();
void FullScreenHotkey(); void FullScreenHotkey();
void StopHotkey(); void StopHotkey();
void ResetHotkey(); void ResetHotkey();

View File

@ -494,6 +494,8 @@ void MainWindow::ConnectHotkeys()
connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close); connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close);
connect(m_hotkey_scheduler, &HotkeyScheduler::TogglePauseHotkey, this, &MainWindow::TogglePause); connect(m_hotkey_scheduler, &HotkeyScheduler::TogglePauseHotkey, this, &MainWindow::TogglePause);
connect(m_hotkey_scheduler, &HotkeyScheduler::ActivateChat, this, &MainWindow::OnActivateChat); 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, connect(m_hotkey_scheduler, &HotkeyScheduler::RefreshGameListHotkey, this,
&MainWindow::RefreshGameList); &MainWindow::RefreshGameList);
connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::RequestStop); connect(m_hotkey_scheduler, &HotkeyScheduler::StopHotkey, this, &MainWindow::RequestStop);
@ -1604,6 +1606,13 @@ void MainWindow::OnActivateChat()
g_netplay_chat_ui->Activate(); g_netplay_chat_ui->Activate();
} }
void MainWindow::OnRequestGolfControl()
{
auto client = Settings::Instance().GetNetPlayClient();
if (client)
client->RequestGolfControl();
}
void MainWindow::ShowTASInput() void MainWindow::ShowTASInput()
{ {
for (int i = 0; i < num_gc_controllers; i++) for (int i = 0; i < num_gc_controllers; i++)

View File

@ -170,6 +170,7 @@ private:
void OnStopRecording(); void OnStopRecording();
void OnExportRecording(); void OnExportRecording();
void OnActivateChat(); void OnActivateChat();
void OnRequestGolfControl();
void ShowTASInput(); void ShowTASInput();
void ChangeDisc(); void ChangeDisc();

View File

@ -85,6 +85,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
const bool strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC); 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 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 sync_all_wii_saves = Config::Get(Config::NETPLAY_SYNC_ALL_WII_SAVES);
const bool golf_mode = Config::Get(Config::NETPLAY_GOLF_MODE);
m_buffer_size_box->setValue(buffer_size); m_buffer_size_box->setValue(buffer_size);
m_save_sd_action->setChecked(write_save_sdcard_data); m_save_sd_action->setChecked(write_save_sdcard_data);
@ -96,6 +97,7 @@ NetPlayDialog::NetPlayDialog(QWidget* parent)
m_strict_settings_sync_action->setChecked(strict_settings_sync); m_strict_settings_sync_action->setChecked(strict_settings_sync);
m_host_input_authority_action->setChecked(host_input_authority); m_host_input_authority_action->setChecked(host_input_authority);
m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves); m_sync_all_wii_saves_action->setChecked(sync_all_wii_saves);
m_golf_mode_action->setChecked(golf_mode);
ConnectWidgets(); ConnectWidgets();
@ -144,6 +146,8 @@ void NetPlayDialog::CreateMainLayout()
m_reduce_polling_rate_action->setCheckable(true); 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 = m_network_menu->addAction(tr("Host Input Authority"));
m_host_input_authority_action->setCheckable(true); 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_other_menu = m_menu_bar->addMenu(tr("Other"));
m_record_input_action = m_other_menu->addAction(tr("Record Inputs")); m_record_input_action = m_other_menu->addAction(tr("Record Inputs"));
@ -306,10 +310,12 @@ void NetPlayDialog::ConnectWidgets()
client->AdjustPadBufferSize(value); 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(); auto server = Settings::Instance().GetNetPlayServer();
if (server) if (server)
server->SetHostInputAuthority(checked); server->SetHostInputAuthority(checked);
m_golf_mode_action->setEnabled(checked);
}); });
connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart); connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart);
@ -355,6 +361,7 @@ void NetPlayDialog::ConnectWidgets()
connect(m_strict_settings_sync_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); connect(m_strict_settings_sync_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
connect(m_host_input_authority_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_sync_all_wii_saves_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
connect(m_golf_mode_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
} }
void NetPlayDialog::SendMessage(const std::string& msg) void NetPlayDialog::SendMessage(const std::string& msg)
@ -482,6 +489,7 @@ void NetPlayDialog::OnStart()
settings.m_SyncCodes = m_sync_codes_action->isChecked(); settings.m_SyncCodes = m_sync_codes_action->isChecked();
settings.m_SyncAllWiiSaves = settings.m_SyncAllWiiSaves =
m_sync_all_wii_saves_action->isChecked() && m_sync_save_data_action->isChecked(); 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 // Unload GameINI to restore things to normal
Config::RemoveLayer(Config::LayerType::GlobalGame); Config::RemoveLayer(Config::LayerType::GlobalGame);
@ -815,6 +823,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled)
m_strict_settings_sync_action->setEnabled(enabled); m_strict_settings_sync_action->setEnabled(enabled);
m_host_input_authority_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_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); m_record_input_action->setEnabled(enabled);
@ -881,6 +890,7 @@ void NetPlayDialog::OnHostInputAuthorityChanged(bool enabled)
const QSignalBlocker blocker(m_host_input_authority_action); const QSignalBlocker blocker(m_host_input_authority_action);
m_host_input_authority_action->setChecked(enabled); m_host_input_authority_action->setChecked(enabled);
m_golf_mode_action->setEnabled(enabled);
} }
else else
{ {
@ -956,6 +966,20 @@ void NetPlayDialog::OnSaveDataSyncFailure()
QueueOnObject(this, [this] { SetOptionsEnabled(true); }); 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() bool NetPlayDialog::IsRecording()
{ {
std::optional<bool> is_recording = RunOnObject(m_record_input_action, &QAction::isChecked); std::optional<bool> is_recording = RunOnObject(m_record_input_action, &QAction::isChecked);
@ -1013,6 +1037,7 @@ void NetPlayDialog::SaveSettings()
Config::SetBase(Config::NETPLAY_STRICT_SETTINGS_SYNC, m_strict_settings_sync_action->isChecked()); 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_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_SYNC_ALL_WII_SAVES, m_sync_all_wii_saves_action->isChecked());
Config::SetBase(Config::NETPLAY_GOLF_MODE, m_golf_mode_action->isChecked());
} }
void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier) void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)

View File

@ -58,6 +58,7 @@ public:
void OnTraversalError(TraversalClient::FailureReason error) override; void OnTraversalError(TraversalClient::FailureReason error) override;
void OnTraversalStateChanged(TraversalClient::State state) override; void OnTraversalStateChanged(TraversalClient::State state) override;
void OnSaveDataSyncFailure() override; void OnSaveDataSyncFailure() override;
void OnGolferChanged(bool is_golfer, const std::string& golfer_name) override;
bool IsRecording() override; bool IsRecording() override;
std::string FindGame(const std::string& game) override; std::string FindGame(const std::string& game) override;
@ -131,6 +132,7 @@ private:
QAction* m_strict_settings_sync_action; QAction* m_strict_settings_sync_action;
QAction* m_host_input_authority_action; QAction* m_host_input_authority_action;
QAction* m_sync_all_wii_saves_action; QAction* m_sync_all_wii_saves_action;
QAction* m_golf_mode_action;
QPushButton* m_quit_button; QPushButton* m_quit_button;
QSplitter* m_splitter; QSplitter* m_splitter;