Skip to content

Commit

Permalink
NetPlay: Implement golf mode
Browse files Browse the repository at this point in the history
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 2bc55de commit 39c670a
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 13 deletions.
1 change: 1 addition & 0 deletions Source/Core/Core/Config/NetplaySettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions Source/Core/Core/Config/NetplaySettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 3 additions & 2 deletions Source/Core/Core/HotkeyManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
Expand Down Expand 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},
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/HotkeyManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum Hotkey
HK_SCREENSHOT,
HK_EXIT,
HK_ACTIVATE_CHAT,
HK_REQUEST_GOLF_CONTROL,

HK_VOLUME_DOWN,
HK_VOLUME_UP,
Expand Down
91 changes: 84 additions & 7 deletions Source/Core/Core/NetPlayClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
{
{
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1735,7 +1797,7 @@ 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 && 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
Expand Down Expand Up @@ -1880,7 +1942,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);
Expand Down Expand Up @@ -1913,7 +1975,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;
Expand All @@ -1937,7 +1999,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];
Expand All @@ -1955,9 +2017,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));
Expand All @@ -1972,6 +2037,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();

Expand All @@ -1995,6 +2061,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())
Expand All @@ -2017,6 +2084,16 @@ void NetPlayClient::SendPowerButtonEvent()
SendAsync(std::move(packet));
}

void NetPlayClient::RequestGolfControl()
{
if (!m_net_settings.m_GolfMode)
return;

sf::Packet packet;
packet << static_cast<MessageId>(NP_MSG_GOLF_REQUEST);
SendAsync(std::move(packet));
}

// called from ---GUI--- thread
bool NetPlayClient::LocalPlayerHasControllerMapped() const
{
Expand Down
8 changes: 8 additions & 0 deletions Source/Core/Core/NetPlayClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -111,6 +112,7 @@ class NetPlayClient : public TraversalClientClient
void SendChatMessage(const std::string& msg);
void RequestStopGame();
void SendPowerButtonEvent();
void RequestGolfControl();

// Send and receive pads values
bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode);
Expand Down Expand Up @@ -179,6 +181,11 @@ 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;

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

Expand Down Expand Up @@ -239,6 +246,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;
Expand Down
9 changes: 9 additions & 0 deletions Source/Core/Core/NetPlayProto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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,
Expand Down
76 changes: 73 additions & 3 deletions Source/Core/Core/NetPlayServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,16 +680,22 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
}

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
{
SendToClients(spac, player.pid);
}
}
break;

case NP_MSG_PAD_HOST_DATA:
{
// Kick player if they're not the host.
if (!player.IsHost())
// Kick player if they're not the golfer.
if (m_current_golfer != 0 && player.pid != m_current_golfer)
return 1;

sf::Packet spac;
Expand Down Expand Up @@ -745,6 +751,55 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
}
break;

case NP_MSG_GOLF_REQUEST:
{
if (m_host_input_authority && m_settings.m_GolfMode && m_pending_golfer == 0 && m_current_golfer != player.pid && PlayerHasControllerMapped(player.pid))
{
m_pending_golfer = player.pid;

sf::Packet spac;
spac << static_cast<MessageId>(NP_MSG_GOLF_PREPARE);
Send(player.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:
{
const u32 ping = (u32)m_ping_timer.GetTimeElapsed();
Expand Down Expand Up @@ -1128,6 +1183,9 @@ bool NetPlayServer::StartGame()
if (!m_host_input_authority)
AdjustPadBufferSize(m_target_buffer_size);

m_current_golfer = 1;
m_pending_golfer = 0;

const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();

const std::string region = SConfig::GetDirectoryForRegion(
Expand Down Expand Up @@ -1212,6 +1270,8 @@ bool NetPlayServer::StartGame()
spac << extension;
}

spac << m_settings.m_GolfMode;

SendAsyncToClients(std::move(spac));

m_start_pending = false;
Expand Down Expand Up @@ -1747,6 +1807,16 @@ 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;
Expand Down
Loading

0 comments on commit 39c670a

Please sign in to comment.