Skip to content
Permalink
Browse files

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.
  • Loading branch information...
Techjar committed Apr 2, 2019
1 parent e2f1da5 commit 1a128763305489df3d691a990bbd6033792dd1e1
@@ -55,5 +55,6 @@ const ConfigInfo<bool> NETPLAY_HOST_INPUT_AUTHORITY{{System::Main, "NetPlay", "H
false};
const ConfigInfo<bool> NETPLAY_SYNC_ALL_WII_SAVES{{System::Main, "NetPlay", "SyncAllWiiSaves"},
false};
const ConfigInfo<bool> NETPLAY_GOLF_MODE{{System::Main, "NetPlay", "GolfMode"}, false};

} // namespace Config
@@ -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_HOST_INPUT_AUTHORITY;
extern const ConfigInfo<bool> NETPLAY_SYNC_ALL_WII_SAVES;
extern const ConfigInfo<bool> NETPLAY_GOLF_MODE;

} // namespace Config
@@ -20,7 +20,7 @@
#include "InputCommon/GCPadStatus.h"

// clang-format off
constexpr std::array<const char*, 132> s_hotkey_labels{{
constexpr std::array<const char*, 133> s_hotkey_labels{{
_trans("Open"),
_trans("Change Disc"),
_trans("Eject Disc"),
@@ -32,6 +32,7 @@ constexpr std::array<const char*, 132> 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<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("Emulation Speed"), HK_DECREASE_EMULATION_SPEED, HK_TOGGLE_THROTTLE},
{_trans("Frame Advance"), HK_FRAME_ADVANCE, HK_FRAME_ADVANCE_RESET_SPEED},
@@ -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
@@ -502,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<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:
{
{
@@ -637,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;
}
@@ -1390,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);
@@ -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
// 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)
{
sf::Packet packet;
@@ -1735,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.
if (m_local_player->pid != m_current_golfer)
{
// 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();
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)
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
{
// run fast if the buffer is overfilled, otherwise run normal speed
SConfig::GetInstance().m_EmulationSpeed = buffer_over_target ? 0.0f : 1.0f;
// 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_local_player->IsHost())
if (m_local_player->pid != m_current_golfer)
{
// add to 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)
{
if (!m_local_player->IsHost())
if (m_local_player->pid != m_current_golfer)
return;

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++)
{
if (m_pad_map[i] == 0)
if (m_pad_map[i] == 0 || m_pad_buffer[i].Size() > 0)
continue;

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

const GCPadStatus& pad_status = m_last_pad_status[pad_num];
m_pad_buffer[pad_num].Push(pad_status);
AddPadStateToPacket(pad_num, pad_status, packet);
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);
}
}

SendAsync(std::move(packet));
@@ -1972,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();

@@ -1995,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())
@@ -2017,6 +2092,22 @@ 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<MessageId>(NP_MSG_GOLF_REQUEST);
packet << pid;
SendAsync(std::move(packet));
}

void NetPlayClient::RequestGolfControl()
{
RequestGolfControl(m_local_player->pid);
}

// called from ---GUI--- thread
bool NetPlayClient::LocalPlayerHasControllerMapped() const
{
@@ -54,6 +54,7 @@ class NetPlayUI
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,8 @@ class NetPlayClient : public TraversalClientClient
void SendChatMessage(const std::string& msg);
void RequestStopGame();
void SendPowerButtonEvent();
void RequestGolfControl(PlayerId pid);
void RequestGolfControl();

// Send and receive pads values
bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode);
@@ -179,6 +182,12 @@ class NetPlayClient : public TraversalClientClient
// 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;

@@ -239,6 +248,7 @@ class NetPlayClient : public TraversalClientClient
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;
@@ -83,6 +83,9 @@ struct NetSettings
std::string m_SaveDataRegion;
bool m_SyncAllWiiSaves;
std::array<int, 4> m_WiimoteExtension;
bool m_GolfMode;

// These aren't sent over the network directly
bool m_IsHosting;
bool m_HostInputAuthority;
};
@@ -128,6 +131,12 @@ enum
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,

0 comments on commit 1a12876

Please sign in to comment.
You can’t perform that action at this time.