|
|
|
@ -90,6 +90,12 @@ static void ShowChatMessage(s32 player_id, const std::string_view& message);
|
|
|
|
|
static void RequestReset(ResetRequestMessage::Reason reason, s32 causing_player_id = 0);
|
|
|
|
|
static void SendConnectRequest();
|
|
|
|
|
|
|
|
|
|
// Spectators
|
|
|
|
|
static bool IsSpectator(const ENetPeer* peer);
|
|
|
|
|
static s32 GetFreeSpectatorSlot();
|
|
|
|
|
static s32 GetSpectatorSlotForPeer(const ENetPeer* peer);
|
|
|
|
|
static void DropSpectator(s32 slot_id, DropPlayerReason reason);
|
|
|
|
|
|
|
|
|
|
// Controlpackets
|
|
|
|
|
static void HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt);
|
|
|
|
|
static void HandleControlMessage(s32 player_id, const ENetPacket* pkt);
|
|
|
|
@ -159,6 +165,12 @@ static std::bitset<MAX_PLAYERS> s_reset_players;
|
|
|
|
|
static Common::Timer s_reset_start_time;
|
|
|
|
|
static Common::Timer s_last_host_connection_attempt;
|
|
|
|
|
|
|
|
|
|
// Spectators
|
|
|
|
|
static std::array<Peer, MAX_SPECTATORS> s_spectators;
|
|
|
|
|
static std::bitset<MAX_SPECTATORS> s_reset_spectators;
|
|
|
|
|
static s32 s_num_spectators = 0;
|
|
|
|
|
static bool s_local_spectating;
|
|
|
|
|
|
|
|
|
|
/// GGPO
|
|
|
|
|
static std::string s_local_nickname;
|
|
|
|
|
static std::string s_local_session_password;
|
|
|
|
@ -238,26 +250,17 @@ static bool SendControlPacket(s32 player_id, const PacketWrapper<T>& pkt)
|
|
|
|
|
return SendControlPacket<T>(s_peers[player_id].peer, pkt);
|
|
|
|
|
}
|
|
|
|
|
template<typename T>
|
|
|
|
|
static void SendControlPacketToAll(const PacketWrapper<T>& pkt)
|
|
|
|
|
static void SendControlPacketToAll(const PacketWrapper<T>& pkt, bool send_to_spectators)
|
|
|
|
|
{
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++)
|
|
|
|
|
const s32 total_peers = send_to_spectators ? MAX_PLAYERS + MAX_SPECTATORS : MAX_PLAYERS;
|
|
|
|
|
for (s32 i = 0; i < total_peers; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!s_peers[i].peer)
|
|
|
|
|
ENetPeer* peer_to_send = i < MAX_PLAYERS ? s_peers[i].peer : s_spectators[i - MAX_PLAYERS].peer;
|
|
|
|
|
if (!peer_to_send)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// last one?
|
|
|
|
|
bool last = true;
|
|
|
|
|
for (s32 j = i + 1; j < MAX_PLAYERS; j++)
|
|
|
|
|
{
|
|
|
|
|
if (s_peers[j].peer)
|
|
|
|
|
{
|
|
|
|
|
last = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ENetPacket* pkt_to_send = last ? pkt.pkt : enet_packet_create(pkt.pkt->data, pkt.pkt->dataLength, pkt.pkt->flags);
|
|
|
|
|
const int rc = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_CONTROL, pkt_to_send);
|
|
|
|
|
ENetPacket* pkt_to_send = enet_packet_create(pkt.pkt->data, pkt.pkt->dataLength, pkt.pkt->flags);
|
|
|
|
|
const int rc = enet_peer_send(peer_to_send, ENET_CHANNEL_CONTROL, pkt_to_send);
|
|
|
|
|
if (rc != 0)
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("enet_peer_send() to player %d failed: %d", i, rc);
|
|
|
|
@ -339,7 +342,7 @@ bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& re
|
|
|
|
|
ENetAddress server_address;
|
|
|
|
|
server_address.host = ENET_HOST_ANY;
|
|
|
|
|
server_address.port = is_hosting ? static_cast<u16>(port) : ENET_PORT_ANY;
|
|
|
|
|
s_enet_host = enet_host_create(&server_address, MAX_PLAYERS - 1, NUM_ENET_CHANNELS, 0, 0);
|
|
|
|
|
s_enet_host = enet_host_create(&server_address, MAX_PLAYERS + MAX_SPECTATORS - 1, NUM_ENET_CHANNELS, 0, 0);
|
|
|
|
|
if (!s_enet_host)
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Failed to create enet host.");
|
|
|
|
@ -351,6 +354,7 @@ bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& re
|
|
|
|
|
s_local_delay = ldelay;
|
|
|
|
|
s_reset_cookie = 0;
|
|
|
|
|
s_reset_players.reset();
|
|
|
|
|
s_reset_spectators.reset();
|
|
|
|
|
|
|
|
|
|
// If we're the host, we can just continue on our merry way, the others will join later.
|
|
|
|
|
if (is_hosting)
|
|
|
|
@ -358,7 +362,9 @@ bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& re
|
|
|
|
|
// Starting session with a single player.
|
|
|
|
|
s_player_id = 0;
|
|
|
|
|
s_num_players = 1;
|
|
|
|
|
s_num_spectators = 0;
|
|
|
|
|
s_reset_players = 1;
|
|
|
|
|
s_reset_spectators = 0;
|
|
|
|
|
s_peers[s_player_id].nickname = s_local_nickname;
|
|
|
|
|
CreateGGPOSession();
|
|
|
|
|
s_state = SessionState::Running;
|
|
|
|
@ -451,10 +457,16 @@ void Netplay::RequestCloseSession(CloseSessionMessage::Reason reason)
|
|
|
|
|
// Notify everyone
|
|
|
|
|
auto pkt = NewControlPacket<CloseSessionMessage>();
|
|
|
|
|
pkt->reason = reason;
|
|
|
|
|
SendControlPacketToAll(pkt);
|
|
|
|
|
SendControlPacketToAll(pkt, true);
|
|
|
|
|
// close spectator connections
|
|
|
|
|
for (s32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s_spectators[i].peer)
|
|
|
|
|
enet_peer_disconnect(s_spectators[i].peer, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close all connections
|
|
|
|
|
// Close player connections
|
|
|
|
|
DestroyGGPOSession();
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++)
|
|
|
|
|
{
|
|
|
|
@ -486,7 +498,14 @@ void Netplay::RequestCloseSession(CloseSessionMessage::Reason reason)
|
|
|
|
|
{
|
|
|
|
|
const s32 player_id = GetPlayerIdForPeer(event.peer);
|
|
|
|
|
if (player_id >= 0)
|
|
|
|
|
{
|
|
|
|
|
s_peers[player_id].peer = nullptr;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const s32 spectator_slot = GetSpectatorSlotForPeer(event.peer);
|
|
|
|
|
if (spectator_slot >= 0)
|
|
|
|
|
s_spectators[spectator_slot].peer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
@ -542,6 +561,14 @@ void Netplay::ShutdownEnetHost()
|
|
|
|
|
s_peers[i] = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s_spectators[i].peer)
|
|
|
|
|
enet_peer_reset(s_spectators[i].peer);
|
|
|
|
|
|
|
|
|
|
s_spectators[i] = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enet_host_destroy(s_enet_host);
|
|
|
|
|
s_enet_host = nullptr;
|
|
|
|
|
}
|
|
|
|
@ -572,7 +599,9 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
|
|
|
|
|
|
|
|
|
|
case ENET_EVENT_TYPE_DISCONNECT:
|
|
|
|
|
{
|
|
|
|
|
const s32 spectator_slot = GetSpectatorSlotForPeer(event->peer);
|
|
|
|
|
const s32 player_id = GetPlayerIdForPeer(event->peer);
|
|
|
|
|
|
|
|
|
|
if (s_state == SessionState::Connecting)
|
|
|
|
|
{
|
|
|
|
|
Assert(player_id == s_host_player_id);
|
|
|
|
@ -586,6 +615,12 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (spectator_slot >= 0)
|
|
|
|
|
{
|
|
|
|
|
DropSpectator(spectator_slot, DropPlayerReason::DisconnectedFromHost);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Log_WarningPrintf("ENet player %d disconnected", player_id);
|
|
|
|
|
if (IsValidPlayerId(player_id))
|
|
|
|
|
{
|
|
|
|
@ -599,8 +634,10 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
|
|
|
|
|
|
|
|
|
|
case ENET_EVENT_TYPE_RECEIVE:
|
|
|
|
|
{
|
|
|
|
|
const s32 player_id = GetPlayerIdForPeer(event->peer);
|
|
|
|
|
if (player_id < 0)
|
|
|
|
|
s32 player_id = GetPlayerIdForPeer(event->peer);
|
|
|
|
|
const s32 spectator_slot = GetSpectatorSlotForPeer(event->peer);
|
|
|
|
|
|
|
|
|
|
if (player_id < 0 && spectator_slot < 0)
|
|
|
|
|
{
|
|
|
|
|
// If it's a new connection, we need to handle connection request messages.
|
|
|
|
|
if (event->channelID == ENET_CHANNEL_CONTROL && IsHost())
|
|
|
|
@ -611,6 +648,9 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
|
|
|
|
|
|
|
|
|
|
if (event->channelID == ENET_CHANNEL_CONTROL)
|
|
|
|
|
{
|
|
|
|
|
if (player_id < 0)
|
|
|
|
|
player_id = spectator_slot + MAX_PLAYERS + 1;
|
|
|
|
|
|
|
|
|
|
HandleControlMessage(player_id, event->packet);
|
|
|
|
|
}
|
|
|
|
|
else if (event->channelID == ENET_CHANNEL_GGPO)
|
|
|
|
@ -736,8 +776,36 @@ void Netplay::CreateGGPOSession()
|
|
|
|
|
cb.free_buffer = NpFreeBuffCb;
|
|
|
|
|
cb.on_event = NpOnEventCb;
|
|
|
|
|
|
|
|
|
|
if (s_local_spectating)
|
|
|
|
|
{
|
|
|
|
|
ggpo_start_spectating(&s_ggpo, &cb, s_num_players, sizeof(Netplay::Input), s_peers[s_host_player_id].peer);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ggpo_start_session(&s_ggpo, &cb, s_num_players, sizeof(Netplay::Input), MAX_ROLLBACK_FRAMES);
|
|
|
|
|
|
|
|
|
|
// if you are the host be sure to add the needed spectators to the session before the players
|
|
|
|
|
// this way we prevent the session finishing to synchronize before adding the spectators.
|
|
|
|
|
if (IsHost())
|
|
|
|
|
{
|
|
|
|
|
for (s32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
// slot filled?
|
|
|
|
|
if (!s_spectators[i].peer)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
GGPOErrorCode result;
|
|
|
|
|
GGPOPlayer player = {sizeof(GGPOPlayer)};
|
|
|
|
|
|
|
|
|
|
player.type = GGPO_PLAYERTYPE_SPECTATOR;
|
|
|
|
|
player.u.remote.peer = s_spectators[i].peer;
|
|
|
|
|
result = ggpo_add_player(s_ggpo, &player, &s_spectators[i].ggpo_handle);
|
|
|
|
|
|
|
|
|
|
// It's a new session, this should always succeed...
|
|
|
|
|
Assert(GGPO_SUCCEEDED(result));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int player_num = 1;
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++)
|
|
|
|
|
{
|
|
|
|
@ -890,7 +958,8 @@ void Netplay::HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt)
|
|
|
|
|
|
|
|
|
|
Log_InfoPrintf("Received session details from host: ");
|
|
|
|
|
Log_InfoPrintf(" Console Region: %s", Settings::GetConsoleRegionDisplayName(msg->settings.console_region));
|
|
|
|
|
Log_InfoPrintf(" BIOS Hash: %s%s", msg->bios_hash.ToString().c_str(), msg->settings.was_fast_booted ? " (fast booted)" : "");
|
|
|
|
|
Log_InfoPrintf(" BIOS Hash: %s%s", msg->bios_hash.ToString().c_str(),
|
|
|
|
|
msg->settings.was_fast_booted ? " (fast booted)" : "");
|
|
|
|
|
Log_InfoPrintf(" Game Serial: %.*s", msg->game_serial_length, msg->GetGameSerial().data());
|
|
|
|
|
Log_InfoPrintf(" Game Title: %.*s", msg->game_title_length, msg->GetGameTitle().data());
|
|
|
|
|
Log_InfoPrintf(" Game Hash: %" PRIX64, msg->game_hash);
|
|
|
|
@ -947,10 +1016,11 @@ void Netplay::SendConnectRequest()
|
|
|
|
|
{
|
|
|
|
|
DebugAssert(!IsHost());
|
|
|
|
|
|
|
|
|
|
Log_DevPrintf("Sending connect request to host with player id %d", s_player_id);
|
|
|
|
|
std::string req = s_local_spectating ? "as a spectator" : fmt::format("with player id {}", s_player_id);
|
|
|
|
|
Log_DevPrintf("Sending connect request to host %s", req.c_str());
|
|
|
|
|
|
|
|
|
|
auto pkt = NewControlPacket<JoinRequestMessage>();
|
|
|
|
|
pkt->mode = JoinRequestMessage::Mode::Player;
|
|
|
|
|
pkt->mode = s_local_spectating ? JoinRequestMessage::Mode::Spectator : JoinRequestMessage::Mode::Player;
|
|
|
|
|
pkt->requested_player_id = s_player_id;
|
|
|
|
|
std::memset(pkt->nickname, 0, sizeof(pkt->nickname));
|
|
|
|
|
std::memset(pkt->session_password, 0, sizeof(pkt->session_password));
|
|
|
|
@ -959,6 +1029,55 @@ void Netplay::SendConnectRequest()
|
|
|
|
|
SendControlPacket(s_peers[s_host_player_id].peer, pkt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Netplay::IsSpectator(const ENetPeer* peer)
|
|
|
|
|
{
|
|
|
|
|
for (s32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s_spectators[i].peer == peer)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s32 Netplay::GetFreeSpectatorSlot()
|
|
|
|
|
{
|
|
|
|
|
for (s32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!s_spectators[i].peer)
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s32 Netplay::GetSpectatorSlotForPeer(const ENetPeer* peer)
|
|
|
|
|
{
|
|
|
|
|
for (s32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s_spectators[i].peer == peer)
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Netplay::DropSpectator(s32 slot_id, DropPlayerReason reason)
|
|
|
|
|
{
|
|
|
|
|
Assert(IsHost());
|
|
|
|
|
DebugAssert(s_spectators[slot_id].peer);
|
|
|
|
|
Log_InfoPrintf("Dropping Spectator %d: %s", slot_id, s_spectators[slot_id].nickname.c_str());
|
|
|
|
|
|
|
|
|
|
Host::OnNetplayMessage(
|
|
|
|
|
fmt::format(Host::TranslateString("Netplay", "Spectator {} left the session: {}").GetCharArray(), slot_id,
|
|
|
|
|
s_spectators[slot_id].nickname, DropPlayerReasonToString(reason)));
|
|
|
|
|
|
|
|
|
|
enet_peer_disconnect_now(s_spectators[slot_id].peer, 0);
|
|
|
|
|
s_spectators[slot_id] = {};
|
|
|
|
|
s_num_spectators--;
|
|
|
|
|
// sadly we have to reset here. this really sucks for the active players since you dont really want to halt for a spectator.
|
|
|
|
|
// not resetting seems to be creating index out of bounds errors in the ringbuffer.
|
|
|
|
|
// TODO ?
|
|
|
|
|
Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Netplay::UpdateConnectingState()
|
|
|
|
|
{
|
|
|
|
|
if (s_reset_start_time.GetTimeSeconds() >= MAX_CONNECT_TIME)
|
|
|
|
@ -1011,11 +1130,34 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Spectators shouldn't get assigned a real player ID, they should go into a separate peer list.
|
|
|
|
|
if (msg->mode != JoinRequestMessage::Mode::Player)
|
|
|
|
|
if (msg->mode == JoinRequestMessage::Mode::Spectator)
|
|
|
|
|
{
|
|
|
|
|
response->result = JoinResponseMessage::Result::SessionClosed;
|
|
|
|
|
// something is really wrong if this isn't the host.
|
|
|
|
|
Assert(IsHost());
|
|
|
|
|
|
|
|
|
|
const s32 spectator_slot = GetFreeSpectatorSlot();
|
|
|
|
|
// no free slots? notify the peer.
|
|
|
|
|
if (spectator_slot < 0)
|
|
|
|
|
{
|
|
|
|
|
response->result = JoinResponseMessage::Result::ServerFull;
|
|
|
|
|
SendControlPacket(peer, response);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert(s_num_spectators < MAX_SPECTATORS);
|
|
|
|
|
response->result = JoinResponseMessage::Result::Success;
|
|
|
|
|
response->player_id = MAX_PLAYERS + 1 + spectator_slot;
|
|
|
|
|
SendControlPacket(peer, response);
|
|
|
|
|
// continue and add peer to the list.
|
|
|
|
|
s_spectators[spectator_slot].peer = peer;
|
|
|
|
|
s_spectators[spectator_slot].nickname = msg->GetNickname();
|
|
|
|
|
s_num_spectators++;
|
|
|
|
|
// Force everyone to resync for now. sadly since ggpo currently only supports adding spectators during setup..
|
|
|
|
|
Reset();
|
|
|
|
|
// notify host that the spectator joined
|
|
|
|
|
Host::OnNetplayMessage(
|
|
|
|
|
fmt::format(Host::TranslateString("Netplay", "{} is joining the session as a Spectator.").GetCharArray(),
|
|
|
|
|
msg->GetNickname()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1123,6 +1265,7 @@ void Netplay::HandleJoinResponseMessage(s32 player_id, const ENetPacket* pkt)
|
|
|
|
|
s_player_id = msg->player_id;
|
|
|
|
|
s_state = SessionState::Resetting;
|
|
|
|
|
s_reset_players.reset();
|
|
|
|
|
s_reset_spectators.reset();
|
|
|
|
|
s_reset_start_time.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1197,9 +1340,10 @@ void Netplay::Reset()
|
|
|
|
|
// Any GGPO packets will get dropped, since the session's gone temporarily.
|
|
|
|
|
DestroyGGPOSession();
|
|
|
|
|
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++)
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS + MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!s_peers[i].peer)
|
|
|
|
|
ENetPeer* peer_to_send = i < MAX_PLAYERS ? s_peers[i].peer : s_spectators[i - MAX_PLAYERS].peer;
|
|
|
|
|
if (!peer_to_send)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
ENetPacket* pkt = enet_packet_create(nullptr, sizeof(header) + state_data_size, ENET_PACKET_FLAG_RELIABLE);
|
|
|
|
@ -1207,7 +1351,7 @@ void Netplay::Reset()
|
|
|
|
|
std::memcpy(pkt->data + sizeof(header), state.GetMemoryPointer(), state_data_size);
|
|
|
|
|
|
|
|
|
|
// This should never fail, we get errors back later..
|
|
|
|
|
const int rc = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_CONTROL, pkt);
|
|
|
|
|
const int rc = enet_peer_send(peer_to_send, ENET_CHANNEL_CONTROL, pkt);
|
|
|
|
|
if (rc != 0)
|
|
|
|
|
Log_ErrorPrintf("enet_peer_send() for synchronization request failed: %d", rc);
|
|
|
|
|
}
|
|
|
|
@ -1222,6 +1366,7 @@ void Netplay::Reset()
|
|
|
|
|
|
|
|
|
|
s_state = SessionState::Resetting;
|
|
|
|
|
s_reset_players.reset();
|
|
|
|
|
s_reset_spectators.reset();
|
|
|
|
|
s_reset_players.set(s_player_id);
|
|
|
|
|
s_reset_start_time.Reset();
|
|
|
|
|
}
|
|
|
|
@ -1246,11 +1391,15 @@ void Netplay::HandleResetMessage(s32 player_id, const ENetPacket* pkt)
|
|
|
|
|
DestroyGGPOSession();
|
|
|
|
|
|
|
|
|
|
// Make sure we're connected to all players.
|
|
|
|
|
Assert(msg->num_players > 1);
|
|
|
|
|
Assert(msg->num_players > 1 || s_local_spectating);
|
|
|
|
|
Log_DevPrintf("Checking connections");
|
|
|
|
|
s_num_players = msg->num_players;
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++)
|
|
|
|
|
{
|
|
|
|
|
// We are already connected to the host as a spectator we dont need any other connections
|
|
|
|
|
if (s_local_spectating)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
Peer& p = s_peers[i];
|
|
|
|
|
if (msg->players[i].controller_port < 0)
|
|
|
|
|
{
|
|
|
|
@ -1314,6 +1463,15 @@ void Netplay::HandleResetMessage(s32 player_id, const ENetPacket* pkt)
|
|
|
|
|
s_state = SessionState::Resetting;
|
|
|
|
|
s_reset_cookie = msg->cookie;
|
|
|
|
|
s_reset_players.reset();
|
|
|
|
|
s_reset_spectators.reset();
|
|
|
|
|
|
|
|
|
|
if (s_local_spectating)
|
|
|
|
|
{
|
|
|
|
|
s_reset_spectators.set(s_player_id - (MAX_PLAYERS + 1));
|
|
|
|
|
s_reset_start_time.Reset();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s_reset_players.set(s_player_id);
|
|
|
|
|
s_reset_start_time.Reset();
|
|
|
|
|
}
|
|
|
|
@ -1329,6 +1487,13 @@ void Netplay::HandleResetCompleteMessage(s32 player_id, const ENetPacket* pkt)
|
|
|
|
|
Log_ErrorPrintf("Received unexpected reset complete from player %d", player_id);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (player_id > MAX_PLAYERS && player_id <= MAX_PLAYERS + MAX_SPECTATORS)
|
|
|
|
|
{
|
|
|
|
|
const s32 spectator_slot = player_id - (MAX_PLAYERS + 1);
|
|
|
|
|
s_reset_spectators.set(spectator_slot);
|
|
|
|
|
Log_DevPrintf("Spectator %d is now reset and ready", spectator_slot);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (s_reset_players.test(player_id))
|
|
|
|
|
{
|
|
|
|
|
Log_ErrorPrintf("Received double reset from player %d", player_id);
|
|
|
|
@ -1363,12 +1528,14 @@ void Netplay::HandleResumeSessionMessage(s32 player_id, const ENetPacket* pkt)
|
|
|
|
|
|
|
|
|
|
void Netplay::UpdateResetState()
|
|
|
|
|
{
|
|
|
|
|
const s32 num_players = (s_local_spectating ? 1 : s_num_players);
|
|
|
|
|
if (IsHost())
|
|
|
|
|
{
|
|
|
|
|
if (static_cast<s32>(s_reset_players.count()) == s_num_players)
|
|
|
|
|
if (static_cast<s32>(s_reset_players.count()) == num_players &&
|
|
|
|
|
static_cast<s32>(s_reset_spectators.count()) == s_num_spectators)
|
|
|
|
|
{
|
|
|
|
|
Log_VerbosePrintf("All players synchronized, resuming!");
|
|
|
|
|
SendControlPacketToAll(NewControlPacket<ResumeSessionMessage>());
|
|
|
|
|
Log_VerbosePrintf("All players and spectators synchronized, resuming!");
|
|
|
|
|
SendControlPacketToAll(NewControlPacket<ResumeSessionMessage>(), true);
|
|
|
|
|
CreateGGPOSession();
|
|
|
|
|
s_state = SessionState::Running;
|
|
|
|
|
return;
|
|
|
|
@ -1388,11 +1555,21 @@ void Netplay::UpdateResetState()
|
|
|
|
|
Log_DevPrintf("Dropping player %d because they didn't connect in time", i);
|
|
|
|
|
DropPlayer(i, DropPlayerReason::ConnectTimeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (s32 i = 0; i < MAX_SPECTATORS; i++)
|
|
|
|
|
{
|
|
|
|
|
if (s_reset_spectators.test(i))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// we'll check if we're done again next loop
|
|
|
|
|
Log_DevPrintf("Dropping Spectator %d because they didn't connect in time", i);
|
|
|
|
|
DropSpectator(i, DropPlayerReason::ConnectTimeout);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (static_cast<s32>(s_reset_players.count()) != s_num_players)
|
|
|
|
|
if (static_cast<s32>(s_reset_players.count()) != num_players)
|
|
|
|
|
{
|
|
|
|
|
for (s32 i = 0; i < MAX_PLAYERS; i++)
|
|
|
|
|
{
|
|
|
|
@ -1403,7 +1580,7 @@ void Netplay::UpdateResetState()
|
|
|
|
|
s_reset_players.set(i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (static_cast<s32>(s_reset_players.count()) == s_num_players)
|
|
|
|
|
if (static_cast<s32>(s_reset_players.count()) == num_players)
|
|
|
|
|
{
|
|
|
|
|
// now connected to all!
|
|
|
|
|
Log_InfoPrintf("Connected to %d players, waiting for host...", s_num_players);
|
|
|
|
@ -1421,8 +1598,14 @@ void Netplay::UpdateResetState()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Log_InfoPrintf("p:%d/s:%d", num_players, s_num_spectators);
|
|
|
|
|
|
|
|
|
|
const s32 min_progress = IsHost() ? static_cast<int>(s_reset_players.count() + s_reset_spectators.count()) :
|
|
|
|
|
static_cast<int>(s_reset_players.count());
|
|
|
|
|
const s32 max_progress = IsHost() ? s_num_players + s_num_spectators : num_players;
|
|
|
|
|
|
|
|
|
|
PollEnet(Common::Timer::GetCurrentValue() + Common::Timer::ConvertMillisecondsToValue(16));
|
|
|
|
|
Host::DisplayLoadingScreen("Netplay synchronizing", 0, static_cast<int>(s_reset_players.count()), s_num_players);
|
|
|
|
|
Host::DisplayLoadingScreen("Netplay synchronizing", 0, min_progress, max_progress);
|
|
|
|
|
Host::PumpMessagesOnCPUThread();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1454,7 +1637,7 @@ void Netplay::NotifyPlayerJoined(s32 player_id)
|
|
|
|
|
{
|
|
|
|
|
auto pkt = NewControlPacket<PlayerJoinedMessage>();
|
|
|
|
|
pkt->player_id = player_id;
|
|
|
|
|
SendControlPacketToAll(pkt);
|
|
|
|
|
SendControlPacketToAll(pkt, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Host::OnNetplayMessage(
|
|
|
|
@ -1498,7 +1681,7 @@ void Netplay::DropPlayer(s32 player_id, DropPlayerReason reason)
|
|
|
|
|
auto pkt = NewControlPacket<DropPlayerMessage>();
|
|
|
|
|
pkt->reason = reason;
|
|
|
|
|
pkt->player_id = player_id;
|
|
|
|
|
SendControlPacketToAll(pkt);
|
|
|
|
|
SendControlPacketToAll(pkt, false);
|
|
|
|
|
|
|
|
|
|
// resync with everyone who's left
|
|
|
|
|
Reset();
|
|
|
|
@ -1830,7 +2013,8 @@ void Netplay::SendChatMessage(const std::string_view& msg)
|
|
|
|
|
|
|
|
|
|
auto pkt = NewControlPacket<ChatMessage>(sizeof(ChatMessage) + static_cast<u32>(msg.length()));
|
|
|
|
|
std::memcpy(pkt.pkt->data + sizeof(ChatMessage), msg.data(), msg.length());
|
|
|
|
|
SendControlPacketToAll(pkt);
|
|
|
|
|
// TODO: turn chat on for spectators? it's kind of weird to handle. probably has to go through the host and be relayed to the players.
|
|
|
|
|
SendControlPacketToAll(pkt, false);
|
|
|
|
|
|
|
|
|
|
// add own netplay message locally to netplay messages
|
|
|
|
|
ShowChatMessage(s_player_id, msg);
|
|
|
|
@ -1876,7 +2060,7 @@ bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std
|
|
|
|
|
// TODO: This is going to blow away our memory cards, because for sync purposes we want all clients
|
|
|
|
|
// to have the same data, and we don't want to trash their local memcards. We should therefore load
|
|
|
|
|
// the memory cards for this game (based on game/global settings), and copy that to the temp card.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: input delay. GGPO Should support changing it on the fly.
|
|
|
|
|
const s32 input_delay = 1;
|
|
|
|
|
|
|
|
|
@ -1894,9 +2078,11 @@ bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Netplay::JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password)
|
|
|
|
|
bool Netplay::JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password,
|
|
|
|
|
bool spectating)
|
|
|
|
|
{
|
|
|
|
|
s_local_session_password = std::move(password);
|
|
|
|
|
s_local_spectating = spectating;
|
|
|
|
|
|
|
|
|
|
// TODO: input delay. GGPO Should support changing it on the fly.
|
|
|
|
|
const s32 input_delay = 1;
|
|
|
|
|