mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-11 13:11:47 +00:00
4813 lines
134 KiB
C++
4813 lines
134 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
#pragma hdrstop
|
|
#include "../idlib/precompiled.h"
|
|
#include "sys_lobby.h"
|
|
|
|
extern idCVar net_connectTimeoutInSeconds;
|
|
extern idCVar net_headlessServer;
|
|
|
|
idCVar net_checkVersion( "net_checkVersion", "0", CVAR_INTEGER, "Check for matching version when clients connect. 0: normal rules, 1: force check, otherwise no check (pass always)" );
|
|
idCVar net_peerTimeoutInSeconds( "net_peerTimeoutInSeconds", "30", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." );
|
|
idCVar net_peerTimeoutInSeconds_Lobby( "net_peerTimeoutInSeconds_Lobby", "20", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." );
|
|
|
|
// NOTE - The snapshot exchange does the bandwidth challenge
|
|
idCVar net_bw_challenge_enable( "net_bw_challenge_enable", "0", CVAR_BOOL, "Enable pre game bandwidth challenge for throttling snap rate" );
|
|
|
|
idCVar net_bw_test_interval( "net_bw_test_interval", "33", CVAR_INTEGER, "MS - how often to send packets in bandwidth test" );
|
|
idCVar net_bw_test_numPackets( "net_bw_test_numPackets", "30", CVAR_INTEGER, "Number of bandwidth challenge packets to send" );
|
|
idCVar net_bw_test_packetSizeBytes( "net_bw_test_packetSizeBytes", "1024", CVAR_INTEGER, "Size of each packet to send out" );
|
|
idCVar net_bw_test_timeout( "net_bw_test_timeout", "500", CVAR_INTEGER, "MS after receiving a bw test packet that client will time out" );
|
|
idCVar net_bw_test_host_timeout( "net_bw_test_host_timeout", "3000", CVAR_INTEGER, "How long host will wait in MS to hear bw results from peers" );
|
|
|
|
idCVar net_bw_test_throttle_rate_pct( "net_bw_test_throttle_rate_pct", "0.80", CVAR_FLOAT, "Min rate % a peer must match in bandwidth challenge before being throttled. 1.0=perfect, 0.0=received nothing" );
|
|
idCVar net_bw_test_throttle_byte_pct( "net_bw_test_throttle_byte_pct", "0.80", CVAR_FLOAT, "Min byte % a peer must match in bandwidth challenge before being throttled. 1.0=perfect (received everything) 0.0=Received nothing" );
|
|
idCVar net_bw_test_throttle_seq_pct( "net_bw_test_throttle_seq_pct", "0.80", CVAR_FLOAT, "Min sequence % a peer must match in bandwidth test before being throttled. 1.0=perfect. This score will be more adversely affected by packet loss than byte %" );
|
|
|
|
idCVar net_ignoreConnects( "net_ignoreConnects", "0", CVAR_INTEGER, "Test as if no one can connect to me. 0 = off, 1 = ignore with no reply, 2 = send goodbye" );
|
|
|
|
idCVar net_skipGoodbye( "net_skipGoodbye", "0", CVAR_BOOL, "" );
|
|
|
|
extern unsigned long NetGetVersionChecksum();
|
|
|
|
/*
|
|
========================
|
|
idLobby::idLobby
|
|
========================
|
|
*/
|
|
idLobby::idLobby()
|
|
{
|
|
lobbyType = TYPE_INVALID;
|
|
sessionCB = NULL;
|
|
|
|
localReadSS = NULL;
|
|
objMemory = NULL;
|
|
haveSubmittedSnaps = false;
|
|
|
|
state = STATE_IDLE;
|
|
failedReason = FAILED_UNKNOWN;
|
|
|
|
host = -1;
|
|
peerIndexOnHost = -1;
|
|
isHost = false;
|
|
needToDisplayMigrateMsg = false;
|
|
migrateMsgFlags = 0;
|
|
|
|
partyToken = 0; // will be initialized later
|
|
loaded = false;
|
|
respondToArbitrate = false;
|
|
waitForPartyOk = false;
|
|
startLoadingFromHost = false;
|
|
|
|
nextSendPingValuesTime = 0;
|
|
lastPingValuesRecvTime = 0;
|
|
|
|
nextSendMigrationGameTime = 0;
|
|
nextSendMigrationGamePeer = 0;
|
|
|
|
bandwidthChallengeStartTime = 0;
|
|
bandwidthChallengeEndTime = 0;
|
|
bandwidthChallengeFinished = false;
|
|
bandwidthChallengeNumGoodSeq = 0;
|
|
|
|
lastSnapBspHistoryUpdateSequence = -1;
|
|
|
|
assert( userList.Max() == freeUsers.Max() );
|
|
assert( userList.Max() == userPool.Max() );
|
|
|
|
userPool.SetNum( userPool.Max() );
|
|
|
|
assert( freeUsers.Num() == 0 );
|
|
assert( freeUsers.Num() == 0 );
|
|
|
|
// Initialize free user list
|
|
for( int i = 0; i < userPool.Num(); i++ )
|
|
{
|
|
freeUsers.Append( &userPool[i] );
|
|
}
|
|
|
|
showHostLeftTheSession = false;
|
|
connectIsFromInvite = false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::Initialize
|
|
========================
|
|
*/
|
|
void idLobby::Initialize( lobbyType_t sessionType_, idSessionCallbacks* callbacks )
|
|
{
|
|
assert( callbacks != NULL );
|
|
|
|
lobbyType = sessionType_;
|
|
sessionCB = callbacks;
|
|
|
|
if( lobbyType == GetActingGameStateLobbyType() )
|
|
{
|
|
// only needed in multiplayer mode
|
|
objMemory = ( uint8* )Mem_Alloc( SNAP_OBJ_JOB_MEMORY, TAG_NETWORKING );
|
|
lzwData = ( lzwCompressionData_t* )Mem_Alloc( sizeof( lzwCompressionData_t ), TAG_NETWORKING );
|
|
}
|
|
}
|
|
|
|
//===============================================================================
|
|
// ** BEGIN PUBLIC INTERFACE ***
|
|
//===============================================================================
|
|
|
|
/*
|
|
========================
|
|
idLobby::StartHosting
|
|
========================
|
|
*/
|
|
void idLobby::StartHosting( const idMatchParameters& parms_ )
|
|
{
|
|
parms = parms_;
|
|
|
|
// Allow common to modify the parms
|
|
common->OnStartHosting( parms );
|
|
|
|
Shutdown(); // Make sure we're in a shutdown state before proceeding
|
|
|
|
assert( GetNumLobbyUsers() == 0 );
|
|
assert( lobbyBackend == NULL );
|
|
|
|
// Get the skill level of all the players that will eventually go into the lobby
|
|
StartCreating();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::StartFinding
|
|
========================
|
|
*/
|
|
void idLobby::StartFinding( const idMatchParameters& parms_ )
|
|
{
|
|
parms = parms_;
|
|
|
|
Shutdown(); // Make sure we're in a shutdown state before proceeding
|
|
|
|
assert( GetNumLobbyUsers() == 0 );
|
|
assert( lobbyBackend == NULL );
|
|
|
|
// Clear search results
|
|
searchResults.Clear();
|
|
|
|
lobbyBackend = sessionCB->FindLobbyBackend( parms, sessionCB->GetPartyLobby().GetNumLobbyUsers(), sessionCB->GetPartyLobby().GetAverageSessionLevel(), idLobbyBackend::TYPE_GAME );
|
|
|
|
SetState( STATE_SEARCHING );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::Pump
|
|
========================
|
|
*/
|
|
void idLobby::Pump()
|
|
{
|
|
|
|
// Check the heartbeat of all our peers, make sure we shouldn't disconnect from peers that haven't sent a heartbeat in awhile
|
|
CheckHeartBeats();
|
|
|
|
UpdateHostMigration();
|
|
|
|
UpdateLocalSessionUsers();
|
|
|
|
switch( state )
|
|
{
|
|
case STATE_IDLE:
|
|
State_Idle();
|
|
break;
|
|
case STATE_CREATE_LOBBY_BACKEND:
|
|
State_Create_Lobby_Backend();
|
|
break;
|
|
case STATE_SEARCHING:
|
|
State_Searching();
|
|
break;
|
|
case STATE_OBTAINING_ADDRESS:
|
|
State_Obtaining_Address();
|
|
break;
|
|
case STATE_CONNECT_HELLO_WAIT:
|
|
State_Connect_Hello_Wait();
|
|
break;
|
|
case STATE_FINALIZE_CONNECT:
|
|
State_Finalize_Connect();
|
|
break;
|
|
case STATE_FAILED:
|
|
break;
|
|
default:
|
|
idLib::Error( "idLobby::Pump: Unknown state." );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ProcessSnapAckQueue
|
|
========================
|
|
*/
|
|
void idLobby::ProcessSnapAckQueue()
|
|
{
|
|
SCOPED_PROFILE_EVENT( "ProcessSnapAckQueue" );
|
|
|
|
const int SNAP_ACKS_TO_PROCESS_PER_FRAME = 1;
|
|
|
|
int numProcessed = 0;
|
|
|
|
while( snapDeltaAckQueue.Num() > 0 && numProcessed < SNAP_ACKS_TO_PROCESS_PER_FRAME )
|
|
{
|
|
if( ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ) )
|
|
{
|
|
numProcessed++;
|
|
}
|
|
snapDeltaAckQueue.RemoveIndex( 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::Shutdown
|
|
========================
|
|
*/
|
|
void idLobby::Shutdown( bool retainMigrationInfo, bool skipGoodbye )
|
|
{
|
|
|
|
// Cancel host migration if we were in the process of it and this is the session type that was migrating
|
|
if( !retainMigrationInfo && migrationInfo.state != MIGRATE_NONE )
|
|
{
|
|
idLib::Printf( "Cancelling host migration on %s.\n", GetLobbyName() );
|
|
EndMigration();
|
|
}
|
|
|
|
failedReason = FAILED_UNKNOWN;
|
|
|
|
if( lobbyBackend == NULL )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: ShutdownLobby (already shutdown) (%s)\n", GetLobbyName() );
|
|
|
|
// If we don't have this lobbyBackend type, we better be properly shutdown for this lobby
|
|
assert( GetNumLobbyUsers() == 0 );
|
|
assert( host == -1 );
|
|
assert( peerIndexOnHost == -1 );
|
|
assert( !isHost );
|
|
assert( lobbyType != GetActingGameStateLobbyType() || !loaded );
|
|
assert( lobbyType != GetActingGameStateLobbyType() || !respondToArbitrate );
|
|
assert( snapDeltaAckQueue.Num() == 0 );
|
|
|
|
// Make sure we don't have old peers connected to this lobby
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
assert( peers[p].GetConnectionState() == CONNECTION_FREE );
|
|
}
|
|
|
|
state = STATE_IDLE;
|
|
|
|
return;
|
|
}
|
|
|
|
NET_VERBOSE_PRINT( "NET: ShutdownLobby (%s)\n", GetLobbyName() );
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].GetConnectionState() != CONNECTION_FREE )
|
|
{
|
|
SetPeerConnectionState( p, CONNECTION_FREE, skipGoodbye ); // This will send goodbye's
|
|
}
|
|
}
|
|
|
|
// Remove any users that weren't handled in ResetPeers
|
|
// (this will happen as a client, because we won't get the reliable msg from the server since we are severing the connection)
|
|
for( int i = 0; i < GetNumLobbyUsers(); i++ )
|
|
{
|
|
lobbyUser_t* user = GetLobbyUser( i );
|
|
UnregisterUser( user );
|
|
}
|
|
|
|
FreeAllUsers();
|
|
|
|
host = -1;
|
|
peerIndexOnHost = -1;
|
|
isHost = false;
|
|
needToDisplayMigrateMsg = false;
|
|
migrationDlg = GDM_INVALID;
|
|
|
|
partyToken = 0; // Reset our party token so we recompute
|
|
loaded = false;
|
|
respondToArbitrate = false;
|
|
waitForPartyOk = false;
|
|
startLoadingFromHost = false;
|
|
|
|
snapDeltaAckQueue.Clear();
|
|
|
|
// Shutdown the lobbyBackend
|
|
if( !retainMigrationInfo )
|
|
{
|
|
sessionCB->DestroyLobbyBackend( lobbyBackend );
|
|
lobbyBackend = NULL;
|
|
}
|
|
|
|
state = STATE_IDLE;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandlePacket
|
|
========================
|
|
*/
|
|
void idLobby::HandlePacket( lobbyAddress_t& remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID )
|
|
{
|
|
SCOPED_PROFILE_EVENT( "HandlePacket" );
|
|
|
|
// msg will hold a fully constructed msg using the packet processor
|
|
byte msgBuffer[ idPacketProcessor::MAX_MSG_SIZE ];
|
|
|
|
idBitMsg msg;
|
|
msg.InitWrite( msgBuffer, sizeof( msgBuffer ) );
|
|
|
|
int peerNum = FindPeer( remoteAddress, sessionID );
|
|
int type = idPacketProcessor::RETURN_TYPE_NONE;
|
|
int userData = 0;
|
|
|
|
if( peerNum >= 0 )
|
|
{
|
|
if( !peers[peerNum].IsActive() )
|
|
{
|
|
idLib::Printf( "NET: Received in-band packet from peer %s with no active connection.\n", remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
type = peers[ peerNum ].packetProc->ProcessIncoming( Sys_Milliseconds(), peers[peerNum].sessionID, fragMsg, msg, userData, peerNum );
|
|
}
|
|
else
|
|
{
|
|
if( !idPacketProcessor::ProcessConnectionlessIncoming( fragMsg, msg, userData ) )
|
|
{
|
|
idLib::Printf( "ProcessConnectionlessIncoming FAILED from %s.\n", remoteAddress.ToString() );
|
|
// Not a valid connectionless packet
|
|
return;
|
|
}
|
|
|
|
// Valid connectionless packets are always RETURN_TYPE_OOB
|
|
type = idPacketProcessor::RETURN_TYPE_OOB;
|
|
|
|
// Find the peer this connectionless msg should go to
|
|
peerNum = FindPeer( remoteAddress, sessionID, true );
|
|
}
|
|
|
|
if( type == idPacketProcessor::RETURN_TYPE_NONE )
|
|
{
|
|
// This packet is not necessarily invalid, it could be a start or middle of a fragmented packet that's not fully constructed.
|
|
return;
|
|
}
|
|
|
|
if( peerNum >= 0 )
|
|
{
|
|
// Update their heart beat (only if we've received a valid packet (we've checked type == idPacketProcessor::RETURN_TYPE_NONE))
|
|
peers[peerNum].lastHeartBeat = Sys_Milliseconds();
|
|
}
|
|
|
|
// Handle server query requests. We do this before the STATE_IDLE check. This is so we respond.
|
|
// We may want to change this to just ignore the request if we are idle, and change the timeout time
|
|
// on the requesters part to just timeout faster.
|
|
if( type == idPacketProcessor::RETURN_TYPE_OOB )
|
|
{
|
|
if( userData == OOB_MATCH_QUERY || userData == OOB_SYSTEMLINK_QUERY )
|
|
{
|
|
sessionCB->HandleServerQueryRequest( remoteAddress, msg, userData );
|
|
return;
|
|
}
|
|
if( userData == OOB_MATCH_QUERY_ACK )
|
|
{
|
|
sessionCB->HandleServerQueryAck( remoteAddress, msg );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if( type == idPacketProcessor::RETURN_TYPE_OOB )
|
|
{
|
|
if( userData == OOB_VOICE_AUDIO )
|
|
{
|
|
sessionCB->HandleOobVoiceAudio( remoteAddress, msg );
|
|
}
|
|
else if( userData == OOB_HELLO )
|
|
{
|
|
// Handle new peer connect request
|
|
peerNum = HandleInitialPeerConnection( msg, remoteAddress, peerNum );
|
|
return;
|
|
}
|
|
else if( userData == OOB_MIGRATE_INVITE )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Migration invite for session %s from %s (state = %s)\n", GetLobbyName(), remoteAddress.ToString(), session->GetStateString() );
|
|
|
|
// Get connection info
|
|
lobbyConnectInfo_t connectInfo;
|
|
connectInfo.ReadFromMsg( msg );
|
|
|
|
if( lobbyBackend != NULL && lobbyBackend->GetState() != idLobbyBackend::STATE_FAILED && lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) // Ignore duplicate invites
|
|
{
|
|
idLib::Printf( "NET: Already migrated to %s.\n", remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
if( migrationInfo.state == MIGRATE_NONE )
|
|
{
|
|
if( IsPeer() && host >= 0 && host < peers.Num() && Sys_Milliseconds() - peers[host].lastHeartBeat > 8 * 1000 )
|
|
{
|
|
// Force migration early if we get an invite, and it has been some time since we've heard from the host
|
|
PickNewHost();
|
|
}
|
|
else
|
|
{
|
|
idLib::Printf( "NET: Ignoring migration invite because we are not migrating %s\n", remoteAddress.ToString() );
|
|
SendGoodbye( remoteAddress ); // So they can remove us from their invite list
|
|
return;
|
|
}
|
|
}
|
|
|
|
if( !sessionCB->PreMigrateInvite( *this ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: sessionCB->PreMigrateInvite( *this ) failed from %s\n", remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
// If we are also becoming a new host, see who wins
|
|
if( migrationInfo.state == MIGRATE_BECOMING_HOST )
|
|
{
|
|
int inviteIndex = FindMigrationInviteIndex( remoteAddress );
|
|
|
|
if( inviteIndex != -1 )
|
|
{
|
|
// We found them in our list, check to make sure our ping is better
|
|
int ping1 = migrationInfo.ourPingMs;
|
|
lobbyUserID_t userId1 = migrationInfo.ourUserId;
|
|
int ping2 = migrationInfo.invites[inviteIndex].pingMs;
|
|
lobbyUserID_t userId2 = migrationInfo.invites[inviteIndex].userId;
|
|
|
|
if( IsBetterHost( ping1, userId1, ping2, userId2 ) )
|
|
{
|
|
idLib::Printf( "NET: Ignoring migration invite from %s, since our ping is better (%i / %i).\n", remoteAddress.ToString(), ping1, ping2 );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fromGame = msg.ReadBool();
|
|
|
|
// Kill the current lobbyBackend
|
|
Shutdown();
|
|
|
|
// Connect to the lobby
|
|
ConnectTo( connectInfo, true ); // Pass in true for the invite flag, so we can connect to invite only lobby if we need to
|
|
|
|
if( verify( sessionCB != NULL ) )
|
|
{
|
|
if( sessionCB->BecomingPeer( *this ) )
|
|
{
|
|
migrationInfo.persistUntilGameEndsData.wasMigratedJoin = true;
|
|
migrationInfo.persistUntilGameEndsData.wasMigratedGame = fromGame;
|
|
}
|
|
}
|
|
|
|
}
|
|
else if( userData == OOB_GOODBYE || userData == OOB_GOODBYE_W_PARTY || userData == OOB_GOODBYE_FULL )
|
|
{
|
|
HandleGoodbyeFromPeer( peerNum, remoteAddress, userData );
|
|
return;
|
|
}
|
|
else if( userData == OOB_RESOURCE_LIST )
|
|
{
|
|
|
|
if( !verify( lobbyType == GetActingGameStateLobbyType() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( peerNum != host )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Resource list from non-host %i, %s\n", peerNum, remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
if( peerNum >= 0 && !peers[peerNum].IsConnected() )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Resource list from host with no game connection: %i, %s\n", peerNum, remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
}
|
|
else if( userData == OOB_BANDWIDTH_TEST )
|
|
{
|
|
int seqNum = msg.ReadLong();
|
|
// TODO: We should read the random data and verify the MD5 checksum
|
|
|
|
int time = Sys_Milliseconds();
|
|
bool inOrder = ( seqNum == 0 || peers[peerNum].bandwidthSequenceNum + 1 == seqNum );
|
|
int timeSinceLast = 0;
|
|
|
|
if( bandwidthChallengeStartTime <= 0 )
|
|
{
|
|
// Reset the test
|
|
NET_VERBOSE_PRINT( "\nNET: Starting bandwidth test @ %d\n", time );
|
|
bandwidthChallengeStartTime = time;
|
|
peers[peerNum].bandwidthSequenceNum = 0;
|
|
peers[peerNum].bandwidthTestBytes = peers[peerNum].packetProc->GetIncomingBytes();
|
|
}
|
|
else
|
|
{
|
|
timeSinceLast = time - ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) );
|
|
}
|
|
|
|
if( inOrder )
|
|
{
|
|
bandwidthChallengeNumGoodSeq++;
|
|
}
|
|
|
|
bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() );
|
|
NET_VERBOSE_PRINT( " NET: %sRecevied OOB bandwidth test %d delta time: %d incoming rate: %.2f incoming rate 2: %d\n", inOrder ? "^2" : "^1", seqNum, timeSinceLast, peers[peerNum].packetProc->GetIncomingRateBytes(), peers[peerNum].packetProc->GetIncomingRate2() );
|
|
peers[peerNum].bandwidthSequenceNum = seqNum;
|
|
|
|
}
|
|
else
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Unknown oob packet %d from %s (%d)\n", userData, remoteAddress.ToString(), peerNum );
|
|
}
|
|
}
|
|
else if( type == idPacketProcessor::RETURN_TYPE_INBAND )
|
|
{
|
|
// Process in-band message
|
|
if( peerNum < 0 )
|
|
{
|
|
idLib::Printf( "NET: In-band message from unknown peer: %s\n", remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
if( !verify( peers[ peerNum ].address.Compare( remoteAddress ) ) )
|
|
{
|
|
idLib::Printf( "NET: Peer with wrong address: %i, %s\n", peerNum, remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
// Handle reliable
|
|
int numReliable = peers[ peerNum ].packetProc->GetNumReliables();
|
|
for( int r = 0; r < numReliable; r++ )
|
|
{
|
|
// Just in case one of the reliable msg's cause this peer to disconnect
|
|
// (this can happen when our party/game host is the same, he quits the game lobby, and sends a reliable msg for us to leave the game)
|
|
peerNum = FindPeer( remoteAddress, sessionID );
|
|
|
|
if( peerNum == -1 )
|
|
{
|
|
idLib::Printf( "NET: Dropped peer while processing reliable msg's: %i, %s\n", peerNum, remoteAddress.ToString() );
|
|
break;
|
|
}
|
|
|
|
const byte* reliableData = peers[ peerNum ].packetProc->GetReliable( r );
|
|
int reliableSize = peers[ peerNum ].packetProc->GetReliableSize( r );
|
|
idBitMsg reliableMsg( reliableData, reliableSize );
|
|
reliableMsg.SetSize( reliableSize );
|
|
|
|
HandleReliableMsg( peerNum, reliableMsg );
|
|
}
|
|
|
|
if( peerNum == -1 || !peers[ peerNum ].IsConnected() )
|
|
{
|
|
// If the peer still has no connection after HandleReliableMsg, then something is wrong.
|
|
// (We could have been in CONNECTION_CONNECTING state for this session type, but the first message
|
|
// we should receive from the server is the ack, otherwise, something went wrong somewhere)
|
|
idLib::Printf( "NET: In-band message from host with no active connection: %i, %s\n", peerNum, remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
// Handle unreliable part (if any)
|
|
if( msg.GetRemainingData() > 0 && loaded )
|
|
{
|
|
if( !verify( lobbyType == GetActingGameStateLobbyType() ) )
|
|
{
|
|
idLib::Printf( "NET: Snapshot msg for non game session lobby %s\n", remoteAddress.ToString() );
|
|
return;
|
|
}
|
|
|
|
if( peerNum == host )
|
|
{
|
|
idSnapShot localSnap;
|
|
int sequence = -1;
|
|
int baseseq = -1;
|
|
bool fullSnap = false;
|
|
localReadSS = &localSnap;
|
|
|
|
// If we are the peer, we assume we only receive snapshot data on the in-band channel
|
|
const byte* deltaData = msg.GetReadData() + msg.GetReadCount();
|
|
int deltaLength = msg.GetRemainingData();
|
|
|
|
if( peers[ peerNum ].snapProc->ReceiveSnapshotDelta( deltaData, deltaLength, 0, sequence, baseseq, localSnap, fullSnap ) )
|
|
{
|
|
|
|
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Got %s snapshot %d delta'd against %d. SS Time: %d\n", ( fullSnap ? "partial" : "full" ), sequence, baseseq, localSnap.GetTime() ) );
|
|
|
|
if( sessionCB->GetState() != idSession::INGAME && sequence != -1 )
|
|
{
|
|
int seq = peers[ peerNum ].snapProc->GetLastAppendedSequence();
|
|
|
|
// When we aren't in the game, we need to send this as reliable msg's, since usercmds won't be taking care of it for us
|
|
byte ackbuffer[32];
|
|
idBitMsg ackmsg( ackbuffer, sizeof( ackbuffer ) );
|
|
ackmsg.WriteLong( seq );
|
|
|
|
// Add incoming BPS for QoS
|
|
float incomingBPS = peers[ peerNum ].receivedBps;
|
|
if( peers[ peerNum ].receivedBpsIndex != seq )
|
|
{
|
|
incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), peers[host].packetProc->GetIncomingRateBytes() );
|
|
peers[ peerNum ].receivedBpsIndex = seq;
|
|
peers[ peerNum ].receivedBps = incomingBPS;
|
|
}
|
|
|
|
ackmsg.WriteQuantizedUFloat< idLobby::BANDWIDTH_REPORTING_MAX, idLobby::BANDWIDTH_REPORTING_BITS >( incomingBPS );
|
|
QueueReliableMessage( host, RELIABLE_SNAPSHOT_ACK, ackbuffer, sizeof( ackbuffer ) );
|
|
}
|
|
}
|
|
|
|
if( fullSnap )
|
|
{
|
|
sessionCB->ReceivedFullSnap();
|
|
common->NetReceiveSnapshot( localSnap );
|
|
}
|
|
|
|
localReadSS = NULL;
|
|
|
|
}
|
|
else
|
|
{
|
|
// If we are the host, we assume we only receive usercmds on the inband channel
|
|
|
|
int snapNum = 0;
|
|
uint16 receivedBps_quantized = 0;
|
|
|
|
byte usercmdBuffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE];
|
|
|
|
lzwCompressionData_t lzwData;
|
|
idLZWCompressor lzwCompressor( &lzwData );
|
|
lzwCompressor.Start( const_cast<byte*>( msg.GetReadData() ) + msg.GetReadCount(), msg.GetRemainingData() );
|
|
lzwCompressor.ReadAgnostic( snapNum );
|
|
lzwCompressor.ReadAgnostic( receivedBps_quantized );
|
|
int usercmdSize = lzwCompressor.Read( usercmdBuffer, sizeof( usercmdBuffer ), true );
|
|
lzwCompressor.End();
|
|
|
|
float receivedBps = ( receivedBps_quantized / ( float )( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) ) * ( float )idLobby::BANDWIDTH_REPORTING_MAX;
|
|
if( peers[ peerNum ].receivedBpsIndex != snapNum )
|
|
{
|
|
peers[ peerNum ].receivedBps = receivedBps;
|
|
peers[ peerNum ].receivedBpsIndex = snapNum;
|
|
}
|
|
|
|
if( snapNum < 50 )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: peer %d ack'd snapNum %d\n", peerNum, snapNum );
|
|
}
|
|
ApplySnapshotDelta( peerNum, snapNum );
|
|
|
|
idBitMsg usercmdMsg( ( const byte* )usercmdBuffer, usercmdSize );
|
|
common->NetReceiveUsercmds( peerNum, usercmdMsg );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HasActivePeers
|
|
========================
|
|
*/
|
|
bool idLobby::HasActivePeers() const
|
|
{
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].GetConnectionState() != CONNECTION_FREE )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::NumFreeSlots
|
|
========================
|
|
*/
|
|
int idLobby::NumFreeSlots() const
|
|
{
|
|
if( parms.matchFlags & MATCH_JOIN_IN_PROGRESS )
|
|
{
|
|
return parms.numSlots - GetNumConnectedUsers();
|
|
}
|
|
else
|
|
{
|
|
return parms.numSlots - GetNumLobbyUsers();
|
|
}
|
|
}
|
|
|
|
//===============================================================================
|
|
// ** END PUBLIC INTERFACE ***
|
|
//===============================================================================
|
|
|
|
//===============================================================================
|
|
// ** BEGIN STATE CODE ***
|
|
//===============================================================================
|
|
|
|
const char* idLobby::stateToString[ NUM_STATES ] =
|
|
{
|
|
ASSERT_ENUM_STRING( STATE_IDLE, 0 ),
|
|
ASSERT_ENUM_STRING( STATE_CREATE_LOBBY_BACKEND, 1 ),
|
|
ASSERT_ENUM_STRING( STATE_SEARCHING, 2 ),
|
|
ASSERT_ENUM_STRING( STATE_OBTAINING_ADDRESS, 3 ),
|
|
ASSERT_ENUM_STRING( STATE_CONNECT_HELLO_WAIT, 4 ),
|
|
ASSERT_ENUM_STRING( STATE_FINALIZE_CONNECT, 5 ),
|
|
ASSERT_ENUM_STRING( STATE_FAILED, 6 ),
|
|
};
|
|
|
|
/*
|
|
========================
|
|
idLobby::State_Idle
|
|
========================
|
|
*/
|
|
void idLobby::State_Idle()
|
|
{
|
|
// If lobbyBackend is in a failed state, shutdown, go to a failed state ourself, and return
|
|
if( lobbyBackend != NULL && lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED )
|
|
{
|
|
HandleConnectionAttemptFailed();
|
|
common->Dialog().ClearDialog( GDM_MIGRATING );
|
|
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
|
|
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
|
|
return;
|
|
}
|
|
|
|
if( migrationInfo.persistUntilGameEndsData.hasGameData && sessionCB->GetState() <= idSession::IDLE )
|
|
{
|
|
// This can happen with 'leaveGame' or 'disconnect' since those paths don't go through endMatch
|
|
// This seems like an ok catch all place but there may be a better way to handle this
|
|
ResetAllMigrationState();
|
|
common->Dialog().ClearDialog( GDM_MIGRATING );
|
|
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
|
|
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::State_Create_Lobby_Backend
|
|
========================
|
|
*/
|
|
void idLobby::State_Create_Lobby_Backend()
|
|
{
|
|
if( !verify( lobbyBackend != NULL ) )
|
|
{
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
|
|
assert( lobbyBackend != NULL );
|
|
|
|
if( migrationInfo.state == MIGRATE_BECOMING_HOST )
|
|
{
|
|
const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 );
|
|
|
|
// If we are taking too long, cancel the connection
|
|
if( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 )
|
|
{
|
|
if( Sys_Milliseconds() - migrationInfo.migrationStartTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS )
|
|
{
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING )
|
|
{
|
|
return; // Busy but valid
|
|
}
|
|
|
|
if( lobbyBackend->GetState() != idLobbyBackend::STATE_READY )
|
|
{
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
|
|
// Success
|
|
InitStateLobbyHost();
|
|
|
|
// Set state to idle to signify to session we are done creating
|
|
SetState( STATE_IDLE );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::State_Searching
|
|
========================
|
|
*/
|
|
void idLobby::State_Searching()
|
|
{
|
|
if( !verify( lobbyBackend != NULL ) )
|
|
{
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
|
|
if( lobbyBackend->GetState() == idLobbyBackend::STATE_SEARCHING )
|
|
{
|
|
return; // Busy but valid
|
|
}
|
|
|
|
if( lobbyBackend->GetState() != idLobbyBackend::STATE_READY )
|
|
{
|
|
SetState( STATE_FAILED ); // Any other lobbyBackend state is invalid
|
|
return;
|
|
}
|
|
|
|
// Done searching, get results from lobbyBackend
|
|
lobbyBackend->GetSearchResults( searchResults );
|
|
|
|
if( searchResults.Num() == 0 )
|
|
{
|
|
// If we didn't get any results, set state to failed
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
|
|
extern idCVar net_maxSearchResultsToTry;
|
|
const int maxSearchResultsToTry = session->GetTitleStorageInt( "net_maxSearchResultsToTry", net_maxSearchResultsToTry.GetInteger() );
|
|
|
|
if( searchResults.Num() > maxSearchResultsToTry )
|
|
{
|
|
searchResults.SetNum( maxSearchResultsToTry );
|
|
}
|
|
|
|
// Set state to idle to signify we are done searching
|
|
SetState( STATE_IDLE );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::State_Obtaining_Address
|
|
========================
|
|
*/
|
|
void idLobby::State_Obtaining_Address()
|
|
{
|
|
if( lobbyBackend->GetState() == idLobbyBackend::STATE_OBTAINING_ADDRESS )
|
|
{
|
|
return; // Valid but not ready
|
|
}
|
|
|
|
if( lobbyBackend->GetState() != idLobbyBackend::STATE_READY )
|
|
{
|
|
// There was an error, signify to caller
|
|
failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
|
|
NET_VERBOSE_PRINT( "idLobby::State_Obtaining_Address: the lobby backend failed." );
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We have the address of the lobbyBackend, we can now send a hello packet
|
|
//
|
|
|
|
// This will be the host for this lobby type
|
|
host = AddPeer( hostAddress, GenerateSessionID() );
|
|
|
|
// Record start time of connection attempt to the host
|
|
helloStartTime = Sys_Milliseconds();
|
|
lastConnectRequest = helloStartTime;
|
|
connectionAttempts = 0;
|
|
|
|
// Change state to connecting
|
|
SetState( STATE_CONNECT_HELLO_WAIT );
|
|
|
|
// Send first connect attempt now (we'll send more periodically if we fail to receive an ack)
|
|
// (we do this after changing state, since the function expects we're in the right state)
|
|
SendConnectionRequest();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::State_Finalize_Connect
|
|
========================
|
|
*/
|
|
void idLobby::State_Finalize_Connect()
|
|
{
|
|
if( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING )
|
|
{
|
|
// Valid but busy
|
|
return;
|
|
}
|
|
|
|
if( lobbyBackend->GetState() != idLobbyBackend::STATE_READY )
|
|
{
|
|
// Any other state not valid, failed
|
|
SetState( STATE_FAILED );
|
|
return;
|
|
}
|
|
|
|
// Success
|
|
SetState( STATE_IDLE );
|
|
|
|
// Tell session mgr if this was a migration
|
|
if( migrationInfo.persistUntilGameEndsData.wasMigratedJoin )
|
|
{
|
|
sessionCB->BecamePeer( *this );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::State_Connect_Hello_Wait
|
|
========================
|
|
*/
|
|
void idLobby::State_Connect_Hello_Wait()
|
|
{
|
|
if( lobbyBackend->GetState() != idLobbyBackend::STATE_READY )
|
|
{
|
|
// If the lobbyBackend is in an error state, shut everything down
|
|
NET_VERBOSE_PRINT( "NET: Lobby is no longer ready while waiting for lobbyType %s hello.\n", GetLobbyName() );
|
|
HandleConnectionAttemptFailed();
|
|
return;
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
|
|
const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000;
|
|
|
|
if( timeoutMs != 0 && time - helloStartTime > timeoutMs )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Timeout waiting for lobbyType %s for party hello.\n", GetLobbyName() );
|
|
HandleConnectionAttemptFailed();
|
|
return;
|
|
}
|
|
|
|
if( connectionAttempts < MAX_CONNECT_ATTEMPTS )
|
|
{
|
|
assert( connectionAttempts >= 1 ); // Should have at least the initial connection attempt
|
|
|
|
// See if we need to send another hello request
|
|
// (keep getting more frequent to increase chance due to possible packet loss, but clamp to MIN_CONNECT_FREQUENCY seconds)
|
|
// TODO: We could eventually make timing out a function of actual number of attempts rather than just plain time.
|
|
int resendTime = Max( MIN_CONNECT_FREQUENCY_IN_SECONDS, CONNECT_REQUEST_FREQUENCY_IN_SECONDS / connectionAttempts ) * 1000;
|
|
|
|
if( time - lastConnectRequest > resendTime )
|
|
{
|
|
SendConnectionRequest();
|
|
lastConnectRequest = time;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SetState
|
|
========================
|
|
*/
|
|
void idLobby::SetState( lobbyState_t newState )
|
|
{
|
|
assert( newState < NUM_STATES );
|
|
assert( state < NUM_STATES );
|
|
|
|
verify_array_size( stateToString, NUM_STATES );
|
|
|
|
if( state == newState )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: idLobby::SetState: State SAME %s for session %s\n", stateToString[ newState ], GetLobbyName() );
|
|
return;
|
|
}
|
|
|
|
// Set the current state
|
|
NET_VERBOSE_PRINT( "NET: idLobby::SetState: State changing from %s to %s for session %s\n", stateToString[ state ], stateToString[ newState ], GetLobbyName() );
|
|
|
|
state = newState;
|
|
}
|
|
|
|
//===============================================================================
|
|
// ** END STATE CODE ***
|
|
//===============================================================================
|
|
|
|
/*
|
|
========================
|
|
idLobby::StartCreating
|
|
========================
|
|
*/
|
|
void idLobby::StartCreating()
|
|
{
|
|
assert( lobbyBackend == NULL );
|
|
assert( state == STATE_IDLE );
|
|
|
|
float skillLevel = GetAverageLocalUserLevel( true );
|
|
|
|
lobbyBackend = sessionCB->CreateLobbyBackend( parms, skillLevel, ( idLobbyBackend::lobbyBackendType_t )lobbyType );
|
|
|
|
SetState( STATE_CREATE_LOBBY_BACKEND );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::FindPeer
|
|
========================
|
|
*/
|
|
int idLobby::FindPeer( const lobbyAddress_t& remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID )
|
|
{
|
|
|
|
bool connectionless = ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ||
|
|
sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ||
|
|
sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE );
|
|
|
|
if( connectionless && !ignoreSessionID )
|
|
{
|
|
return -1; // This was meant to be connectionless. FindPeer is meant for connected (or connecting) peers
|
|
}
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].GetConnectionState() == CONNECTION_FREE )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( peers[p].address.Compare( remoteAddress ) )
|
|
{
|
|
if( connectionless && ignoreSessionID )
|
|
{
|
|
return p;
|
|
}
|
|
|
|
// Using a rolling check, so that we account for possible packet loss, and out of order issues
|
|
if( IsPeer() )
|
|
{
|
|
idPacketProcessor::sessionId_t searchStart = peers[p].sessionID;
|
|
|
|
// Since we only roll the code between matches, we should only need to look ahead a couple increments.
|
|
// Worse case, if the stars line up, the client doesn't see the new sessionId, and times out, and gets booted.
|
|
// This should be impossible though, since the timings won't be possible considering how long it takes to end the match,
|
|
// and restart, and then restart again.
|
|
int numTries = 2;
|
|
|
|
while( numTries-- > 0 && searchStart != sessionID )
|
|
{
|
|
searchStart = IncrementSessionID( searchStart );
|
|
if( searchStart == sessionID )
|
|
{
|
|
idLib::Printf( "NET: Rolling session ID check found new ID: %i\n", searchStart );
|
|
if( peers[p].packetProc != NULL )
|
|
{
|
|
peers[p].packetProc->VerifyEmptyReliableQueue( RELIABLE_GAME_DATA, RELIABLE_DUMMY_MSG );
|
|
}
|
|
peers[p].sessionID = searchStart;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( peers[p].sessionID != sessionID )
|
|
{
|
|
continue;
|
|
}
|
|
return p;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::FindAnyPeer
|
|
Find a peer when we don't know the session id, and we don't care since it's a connectionless msg
|
|
========================
|
|
*/
|
|
int idLobby::FindAnyPeer( const lobbyAddress_t& remoteAddress ) const
|
|
{
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].GetConnectionState() == CONNECTION_FREE )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( peers[p].address.Compare( remoteAddress ) )
|
|
{
|
|
return p;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::FindFreePeer
|
|
========================
|
|
*/
|
|
int idLobby::FindFreePeer() const
|
|
{
|
|
|
|
// Return the first non active peer
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( !peers[p].IsActive() )
|
|
{
|
|
return p;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::AddPeer
|
|
========================
|
|
*/
|
|
int idLobby::AddPeer( const lobbyAddress_t& remoteAddress, idPacketProcessor::sessionId_t sessionID )
|
|
{
|
|
// First, make sure we don't already have this peer
|
|
int p = FindPeer( remoteAddress, sessionID );
|
|
assert( p == -1 ); // When using session ID's, we SHOULDN'T find this remoteAddress/sessionID combo
|
|
|
|
if( p == -1 )
|
|
{
|
|
// If we didn't find the peer, we need to add a new one
|
|
|
|
p = FindFreePeer();
|
|
|
|
if( p == -1 )
|
|
{
|
|
peer_t newPeer;
|
|
p = peers.Append( newPeer );
|
|
}
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
peer.ResetAllData();
|
|
|
|
assert( peer.connectionState == CONNECTION_FREE );
|
|
|
|
peer.address = remoteAddress;
|
|
|
|
peer.sessionID = sessionID;
|
|
|
|
NET_VERBOSE_PRINT( "NET: Added peer %s at index %i\n", remoteAddress.ToString(), p );
|
|
}
|
|
else
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Found peer %s at index %i\n", remoteAddress.ToString(), p );
|
|
}
|
|
|
|
SetPeerConnectionState( p, CONNECTION_CONNECTING );
|
|
|
|
if( lobbyType == GetActingGameStateLobbyType() )
|
|
{
|
|
// Reset various flags used in game mode
|
|
peers[p].ResetMatchData();
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::DisconnectPeerFromSession
|
|
========================
|
|
*/
|
|
void idLobby::DisconnectPeerFromSession( int p )
|
|
{
|
|
if( !verify( IsHost() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
if( peer.GetConnectionState() != CONNECTION_FREE )
|
|
{
|
|
SetPeerConnectionState( p, CONNECTION_FREE );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::DisconnectAllPeers
|
|
========================
|
|
*/
|
|
void idLobby::DisconnectAllPeers()
|
|
{
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
DisconnectPeerFromSession( p );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendGoodbye
|
|
========================
|
|
*/
|
|
void idLobby::SendGoodbye( const lobbyAddress_t& remoteAddress, bool wasFull )
|
|
{
|
|
|
|
if( net_skipGoodbye.GetBool() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
NET_VERBOSE_PRINT( "NET: Sending goodbye to %s for %s (wasFull = %i)\n", remoteAddress.ToString(), GetLobbyName(), wasFull );
|
|
|
|
static const int NUM_REDUNDANT_GOODBYES = 10;
|
|
|
|
int msgType = OOB_GOODBYE;
|
|
|
|
if( wasFull )
|
|
{
|
|
msgType = OOB_GOODBYE_FULL;
|
|
}
|
|
else if( lobbyType == TYPE_GAME && ( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) && !( parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) )
|
|
{
|
|
msgType = OOB_GOODBYE_W_PARTY;
|
|
}
|
|
|
|
for( int i = 0; i < NUM_REDUNDANT_GOODBYES; i++ )
|
|
{
|
|
SendConnectionLess( remoteAddress, msgType );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SetPeerConnectionState
|
|
========================
|
|
*/
|
|
void idLobby::SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye )
|
|
{
|
|
|
|
if( !verify( p >= 0 && p < peers.Num() ) )
|
|
{
|
|
idLib::Printf( "NET: SetPeerConnectionState invalid peer index %i\n", p );
|
|
return;
|
|
}
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
|
|
|
|
if( peer.GetConnectionState() == newState )
|
|
{
|
|
idLib::Printf( "NET: SetPeerConnectionState: Peer already in state %i\n", newState );
|
|
assert( 0 ); // This case means something is most likely bad, and it's the programmers fault
|
|
assert( ( peer.packetProc != NULL ) == peer.IsActive() );
|
|
assert( ( ( peer.snapProc != NULL ) == peer.IsActive() ) == ( actingGameStateLobbyType == lobbyType ) );
|
|
return;
|
|
}
|
|
|
|
if( newState == CONNECTION_CONNECTING )
|
|
{
|
|
//mem.PushHeap();
|
|
|
|
// We better be coming from a free connection state if we are trying to connect
|
|
assert( peer.GetConnectionState() == CONNECTION_FREE );
|
|
|
|
assert( peer.packetProc == NULL );
|
|
peer.packetProc = new( TAG_NETWORKING )idPacketProcessor();
|
|
|
|
if( lobbyType == actingGameStateLobbyType )
|
|
{
|
|
assert( peer.snapProc == NULL );
|
|
peer.snapProc = new( TAG_NETWORKING )idSnapshotProcessor();
|
|
}
|
|
|
|
//mem.PopHeap();
|
|
}
|
|
else if( newState == CONNECTION_ESTABLISHED )
|
|
{
|
|
// If we are marking this peer as connected for the first time, make sure this peer was actually trying to connect.
|
|
assert( peer.GetConnectionState() == CONNECTION_CONNECTING );
|
|
}
|
|
else if( newState == CONNECTION_FREE )
|
|
{
|
|
// If we are freeing this connection and we had an established connection before, make sure to send a goodbye
|
|
if( peer.GetConnectionState() == CONNECTION_ESTABLISHED && !skipGoodbye )
|
|
{
|
|
idLib::Printf( "SetPeerConnectionState: Sending goodbye to peer %s from session %s\n", peer.address.ToString(), GetLobbyName() );
|
|
SendGoodbye( peer.address );
|
|
}
|
|
}
|
|
|
|
peer.connectionState = newState;
|
|
|
|
if( !peer.IsActive() )
|
|
{
|
|
if( peer.packetProc != NULL )
|
|
{
|
|
delete peer.packetProc;
|
|
peer.packetProc = NULL;
|
|
}
|
|
|
|
if( peer.snapProc != NULL )
|
|
{
|
|
assert( lobbyType == actingGameStateLobbyType );
|
|
delete peer.snapProc;
|
|
peer.snapProc = NULL;
|
|
}
|
|
}
|
|
|
|
// Do this in case we disconnected the peer
|
|
if( IsHost() )
|
|
{
|
|
RemoveUsersWithDisconnectedPeers();
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::QueueReliableMessage
|
|
========================
|
|
*/
|
|
void idLobby::QueueReliableMessage( int p, byte type, const byte* data, int dataLen )
|
|
{
|
|
if( !verify( p >= 0 && p < peers.Num() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
// Don't send to this peer if we don't have an established connection of this session type
|
|
NET_VERBOSE_PRINT( "NET: Not sending reliable type %i to peer %i because connectionState is %i\n", type, p, peer.GetConnectionState() );
|
|
return;
|
|
}
|
|
|
|
if( peer.packetProc->NumQueuedReliables() > 2 )
|
|
{
|
|
idLib::PrintfIf( false, "NET: peer.packetProc->NumQueuedReliables() > 2: %i (%i / %s)\n", peer.packetProc->NumQueuedReliables(), p, peer.address.ToString() );
|
|
}
|
|
|
|
if( !peer.packetProc->QueueReliableMessage( type, data, dataLen ) )
|
|
{
|
|
// For now, when this happens, disconnect from all session types
|
|
NET_VERBOSE_PRINT( "NET: Dropping peer because we overflowed his reliable message queue\n" );
|
|
if( IsHost() )
|
|
{
|
|
// Disconnect peer from this session type
|
|
DisconnectPeerFromSession( p );
|
|
}
|
|
else
|
|
{
|
|
Shutdown(); // Shutdown session if we can't queue the reliable
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetNumConnectedPeers
|
|
========================
|
|
*/
|
|
int idLobby::GetNumConnectedPeers() const
|
|
{
|
|
int numConnected = 0;
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
if( peers[i].IsConnected() )
|
|
{
|
|
numConnected++;
|
|
}
|
|
}
|
|
|
|
return numConnected;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetNumConnectedPeersInGame
|
|
========================
|
|
*/
|
|
int idLobby::GetNumConnectedPeersInGame() const
|
|
{
|
|
int numActive = 0;
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
if( peers[i].IsConnected() && peers[i].inGame )
|
|
{
|
|
numActive++;
|
|
}
|
|
}
|
|
|
|
return numActive;
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendMatchParmsToPeers
|
|
========================
|
|
*/
|
|
void idLobby::SendMatchParmsToPeers()
|
|
{
|
|
if( !IsHost() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( GetNumConnectedPeers() == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
|
|
idBitMsg msg( buffer, sizeof( buffer ) );
|
|
parms.Write( msg );
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( !peers[p].IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
QueueReliableMessage( p, RELIABLE_MATCH_PARMS, msg.GetReadData(), msg.GetSize() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
STATIC idLobby::IsReliablePlayerToPlayerType
|
|
========================
|
|
*/
|
|
bool idLobby::IsReliablePlayerToPlayerType( byte type )
|
|
{
|
|
return ( type >= RELIABLE_PLAYER_TO_PLAYER_BEGIN ) && ( type < RELIABLE_PLAYER_TO_PLAYER_END );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleReliablePlayerToPlayerMsg
|
|
========================
|
|
*/
|
|
void idLobby::HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg& msg, int type )
|
|
{
|
|
reliablePlayerToPlayerHeader_t info;
|
|
int c, b;
|
|
msg.SaveReadState( c, b ); // in case we need to forward or fail
|
|
|
|
if( !info.Read( this, msg ) )
|
|
{
|
|
idLib::Warning( "NET: Ignoring invalid reliable player to player message" );
|
|
msg.RestoreReadState( c, b );
|
|
return;
|
|
}
|
|
|
|
const bool isForLocalPlayer = IsSessionUserIndexLocal( info.toSessionUserIndex );
|
|
|
|
if( isForLocalPlayer )
|
|
{
|
|
HandleReliablePlayerToPlayerMsg( info, msg, type );
|
|
}
|
|
else if( IsHost() )
|
|
{
|
|
const int targetPeer = PeerIndexForSessionUserIndex( info.toSessionUserIndex );
|
|
msg.RestoreReadState( c, b );
|
|
// forward the rest of the data
|
|
const byte* data = msg.GetReadData() + msg.GetReadCount();
|
|
int dataLen = msg.GetSize() - msg.GetReadCount();
|
|
|
|
QueueReliableMessage( targetPeer, type, data, dataLen );
|
|
}
|
|
else
|
|
{
|
|
idLib::Warning( "NET: Can't forward reliable message for remote player: I'm not the host" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleReliablePlayerToPlayerMsg
|
|
========================
|
|
*/
|
|
void idLobby::HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t& info, idBitMsg& msg, int reliableType )
|
|
{
|
|
#if 0
|
|
// Remember that the reliablePlayerToPlayerHeader_t was already removed from the msg
|
|
reliablePlayerToPlayer_t type = ( reliablePlayerToPlayer_t )( reliableType - RELIABLE_PLAYER_TO_PLAYER_BEGIN );
|
|
|
|
switch( type )
|
|
{
|
|
case RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT:
|
|
{
|
|
sessionCB->HandleReliableVoiceEvent( *this, info.fromSessionUserIndex, info.toSessionUserIndex, msg );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
idLib::Warning( "NET: Ignored unknown player to player reliable type %i", ( int ) type );
|
|
}
|
|
};
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendConnectionLess
|
|
========================
|
|
*/
|
|
void idLobby::SendConnectionLess( const lobbyAddress_t& remoteAddress, byte type, const byte* data, int dataLen )
|
|
{
|
|
idBitMsg msg( data, dataLen );
|
|
msg.SetSize( dataLen );
|
|
|
|
byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ];
|
|
idBitMsg processedMsg( buffer, sizeof( buffer ) );
|
|
|
|
// Process the send
|
|
idPacketProcessor::ProcessConnectionlessOutgoing( msg, processedMsg, lobbyType, type );
|
|
|
|
const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
|
|
|
|
// Send it
|
|
sessionCB->SendRawPacket( remoteAddress, processedMsg.GetReadData(), processedMsg.GetSize(), useDirectPort );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendConnectionRequest
|
|
========================
|
|
*/
|
|
void idLobby::SendConnectionRequest()
|
|
{
|
|
// Some sanity checking
|
|
assert( state == STATE_CONNECT_HELLO_WAIT );
|
|
assert( peers[host].GetConnectionState() == CONNECTION_CONNECTING );
|
|
assert( GetNumLobbyUsers() == 0 );
|
|
|
|
// Buffer to hold connect msg
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
|
|
idBitMsg msg( buffer, sizeof( buffer ) );
|
|
|
|
// Add the current version info to the handshake
|
|
const unsigned long localChecksum = NetGetVersionChecksum();
|
|
|
|
NET_VERBOSE_PRINT( "NET: version = %i\n", localChecksum );
|
|
|
|
msg.WriteLong( localChecksum );
|
|
msg.WriteUShort( peers[host].sessionID );
|
|
msg.WriteBool( connectIsFromInvite );
|
|
|
|
// We use InitSessionUsersFromLocalUsers here to copy the current local users over to session users simply to have a list
|
|
// to send on the initial connection attempt. We immediately clear our session user list once sent.
|
|
InitSessionUsersFromLocalUsers( true );
|
|
|
|
if( GetNumLobbyUsers() > 0 )
|
|
{
|
|
// Fill up the msg with the users on this machine
|
|
msg.WriteByte( GetNumLobbyUsers() );
|
|
|
|
for( int u = 0; u < GetNumLobbyUsers(); u++ )
|
|
{
|
|
GetLobbyUser( u )->WriteToMsg( msg );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FreeAllUsers();
|
|
SetState( STATE_FAILED );
|
|
|
|
return;
|
|
}
|
|
|
|
// We just used these users to fill up the msg above, we will get the real list from the server if we connect.
|
|
FreeAllUsers();
|
|
|
|
NET_VERBOSE_PRINT( "NET: Sending hello to: %s (lobbyType: %s, session ID %i, attempt: %i)\n", hostAddress.ToString(), GetLobbyName(), peers[host].sessionID, connectionAttempts );
|
|
|
|
SendConnectionLess( hostAddress, OOB_HELLO, msg.GetReadData(), msg.GetSize() );
|
|
|
|
connectionAttempts++;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ConnectTo
|
|
|
|
Fires off a request to get the address of a lobbyBackend owner, and then attempts to connect (eventually handled in HandleObtainingLobbyOwnerAddress)
|
|
========================
|
|
*/
|
|
void idLobby::ConnectTo( const lobbyConnectInfo_t& connectInfo, bool fromInvite )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: idSessionLocal::ConnectTo: fromInvite = %i\n", fromInvite );
|
|
|
|
// Make sure current session is shutdown
|
|
Shutdown();
|
|
|
|
connectIsFromInvite = fromInvite;
|
|
|
|
lobbyBackend = sessionCB->JoinFromConnectInfo( connectInfo, ( idLobbyBackend::lobbyBackendType_t )lobbyType );
|
|
|
|
// First, we need the address of the lobbyBackend owner
|
|
lobbyBackend->GetOwnerAddress( hostAddress );
|
|
|
|
SetState( STATE_OBTAINING_ADDRESS );
|
|
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleGoodbyeFromPeer
|
|
========================
|
|
*/
|
|
void idLobby::HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t& remoteAddress, int msgType )
|
|
{
|
|
if( migrationInfo.state != MIGRATE_NONE )
|
|
{
|
|
// If this peer is on our invite list, remove them
|
|
for( int i = 0; i < migrationInfo.invites.Num(); i++ )
|
|
{
|
|
if( migrationInfo.invites[i].address.Compare( remoteAddress, true ) )
|
|
{
|
|
migrationInfo.invites.RemoveIndex( i );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( peerNum < 0 )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Goodbye from unknown peer %s on session %s\n", remoteAddress.ToString(), GetLobbyName() );
|
|
return;
|
|
}
|
|
|
|
if( peers[peerNum].GetConnectionState() == CONNECTION_FREE )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Goodbye from peer %s on session %s that is not connected\n", remoteAddress.ToString(), GetLobbyName() );
|
|
return;
|
|
}
|
|
|
|
if( IsHost() )
|
|
{
|
|
// Goodbye from peer, remove him
|
|
NET_VERBOSE_PRINT( "NET: Goodbye from peer %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
|
|
DisconnectPeerFromSession( peerNum );
|
|
}
|
|
else
|
|
{
|
|
// Let session handler take care of this
|
|
NET_VERBOSE_PRINT( "NET: Goodbye from host %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
|
|
sessionCB->GoodbyeFromHost( *this, peerNum, remoteAddress, msgType );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleGoodbyeFromPeer
|
|
========================
|
|
*/
|
|
void idLobby::HandleConnectionAttemptFailed()
|
|
{
|
|
Shutdown();
|
|
failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
|
|
SetState( STATE_FAILED );
|
|
|
|
if( migrationInfo.persistUntilGameEndsData.wasMigratedJoin )
|
|
{
|
|
sessionCB->FailedGameMigration( *this );
|
|
}
|
|
|
|
ResetAllMigrationState();
|
|
|
|
needToDisplayMigrateMsg = false;
|
|
migrateMsgFlags = 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ConnectToNextSearchResult
|
|
========================
|
|
*/
|
|
bool idLobby::ConnectToNextSearchResult()
|
|
{
|
|
if( lobbyType != TYPE_GAME )
|
|
{
|
|
return false; // Only game sessions use matchmaking searches
|
|
}
|
|
|
|
// End current session lobby (this WON'T free search results)
|
|
Shutdown();
|
|
|
|
if( searchResults.Num() == 0 )
|
|
{
|
|
return false; // No more search results to connect to, give up
|
|
}
|
|
|
|
// Get next search result
|
|
lobbyConnectInfo_t connectInfo = searchResults[0];
|
|
|
|
// Remove this search result
|
|
searchResults.RemoveIndex( 0 );
|
|
|
|
// If we are connecting to a game lobby, tell our party to connect to this lobby as well
|
|
if( lobbyType == TYPE_GAME && sessionCB->GetPartyLobby().IsLobbyActive() )
|
|
{
|
|
sessionCB->GetPartyLobby().SendMembersToLobby( lobbyType, connectInfo, true );
|
|
}
|
|
|
|
// Attempt to connect the lobby
|
|
ConnectTo( connectInfo, true ); // Pass in true for invite, since searches are for matchmaking, and we should always be able to connect to those types of matches
|
|
|
|
// Clear the "Lobby was Full" dialog in case it's up, since we are going to try to connect to a different lobby now
|
|
common->Dialog().ClearDialog( GDM_LOBBY_FULL );
|
|
|
|
return true; // Notify caller we are attempting to connect
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::CheckVersion
|
|
========================
|
|
*/
|
|
bool idLobby::CheckVersion( idBitMsg& msg, lobbyAddress_t peerAddress )
|
|
{
|
|
const unsigned long remoteChecksum = msg.ReadLong();
|
|
|
|
if( net_checkVersion.GetInteger() == 1 )
|
|
{
|
|
const unsigned long localChecksum = NetGetVersionChecksum();
|
|
|
|
NET_VERBOSE_PRINT( "NET: Comparing handshake version - localChecksum = %i, remoteChecksum = %i\n", localChecksum, remoteChecksum );
|
|
return ( remoteChecksum == localChecksum );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::VerifyNumConnectingUsers
|
|
Make sure number of users connecting is valid, and make sure we have enough room
|
|
========================
|
|
*/
|
|
bool idLobby::VerifyNumConnectingUsers( idBitMsg& msg )
|
|
{
|
|
int c, b;
|
|
msg.SaveReadState( c, b );
|
|
const int numUsers = msg.ReadByte();
|
|
msg.RestoreReadState( c, b );
|
|
|
|
const int numFreeSlots = NumFreeSlots();
|
|
|
|
NET_VERBOSE_PRINT( "NET: VerifyNumConnectingUsers %i users, %i free slots for %s\n", numUsers, numFreeSlots, GetLobbyName() );
|
|
|
|
if( numUsers <= 0 || numUsers > MAX_PLAYERS - 1 )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Invalid numUsers %i\n", numUsers );
|
|
return false;
|
|
}
|
|
else if( numUsers > numFreeSlots )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available\n", numUsers, numFreeSlots );
|
|
return false;
|
|
}
|
|
else if( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY && sessionCB->GetGameLobby().IsLobbyActive() && !IsMigrating() )
|
|
{
|
|
const int numFreeGameSlots = sessionCB->GetGameLobby().NumFreeSlots();
|
|
|
|
if( numUsers > numFreeGameSlots )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available on the active game session\n", numUsers, numFreeGameSlots );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::VerifyLobbyUserIDs
|
|
========================
|
|
*/
|
|
bool idLobby::VerifyLobbyUserIDs( idBitMsg& msg )
|
|
{
|
|
int c, b;
|
|
msg.SaveReadState( c, b );
|
|
const int numUsers = msg.ReadByte();
|
|
|
|
// Add the new users to our own list
|
|
for( int u = 0; u < numUsers; u++ )
|
|
{
|
|
lobbyUser_t newUser;
|
|
|
|
// Read in the new user
|
|
newUser.ReadFromMsg( msg );
|
|
|
|
if( GetLobbyUserIndexByID( newUser.lobbyUserID, true ) != -1 )
|
|
{
|
|
msg.RestoreReadState( c, b );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
msg.RestoreReadState( c, b );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleInitialPeerConnection
|
|
Received on an initial peer connect request (OOB_HELLO)
|
|
========================
|
|
*/
|
|
int idLobby::HandleInitialPeerConnection( idBitMsg& msg, const lobbyAddress_t& peerAddress, int peerNum )
|
|
{
|
|
if( net_ignoreConnects.GetInteger() > 0 )
|
|
{
|
|
if( net_ignoreConnects.GetInteger() == 2 )
|
|
{
|
|
SendGoodbye( peerAddress );
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if( !IsHost() )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Got connectionless hello from peer %s on session, and we are not a host\n", peerAddress.ToString() );
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
|
|
// See if this is a peer migrating to us, if so, remove them from our invite list
|
|
bool migrationInvite = false;
|
|
int migrationGameData = -1;
|
|
|
|
|
|
for( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- )
|
|
{
|
|
if( migrationInfo.invites[i].address.Compare( peerAddress, true ) )
|
|
{
|
|
|
|
migrationGameData = migrationInfo.invites[i].migrationGameData;
|
|
migrationInfo.invites.RemoveIndex( i ); // Remove this peer from the list, since this peer will now be connected (or rejected, either way we don't want to keep sending invites)
|
|
migrationInvite = true;
|
|
NET_VERBOSE_PRINT( "^2NET: Response from migration invite %s. GameData: %d\n", peerAddress.ToString(), migrationGameData );
|
|
}
|
|
}
|
|
|
|
if( !MatchTypeIsJoinInProgress( parms.matchFlags ) && lobbyType == TYPE_GAME && migrationInfo.persistUntilGameEndsData.wasMigratedHost && IsMigratedStatsGame() && !migrationInvite )
|
|
{
|
|
// No matter what, don't let people join migrated game sessions that are going to continue on to the same game
|
|
// Not on invite list in a migrated game session - bounce him
|
|
NET_VERBOSE_PRINT( "NET: Denying game connection from %s since not on migration invite list\n", peerAddress.ToString() );
|
|
for( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- )
|
|
{
|
|
NET_VERBOSE_PRINT( " Invite[%d] addr: %s\n", i, migrationInfo.invites[i].address.ToString() );
|
|
}
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
|
|
|
|
if( MatchTypeIsJoinInProgress( parms.matchFlags ) )
|
|
{
|
|
// If this is for a game connection, make sure we have a game lobby
|
|
if( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() < idSession::GAME_LOBBY )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Denying game connection from %s because we don't have a game lobby\n", peerAddress.ToString() );
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If this is for a game connection, make sure we are in the game lobby
|
|
if( lobbyType == TYPE_GAME && sessionCB->GetState() != idSession::GAME_LOBBY )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Denying game connection from %s while not in game lobby\n", peerAddress.ToString() );
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
|
|
// If this is for a party connection, make sure we are not in game, unless this was for host migration invite
|
|
if( !migrationInvite && lobbyType == TYPE_PARTY && ( sessionCB->GetState() == idSession::INGAME || sessionCB->GetState() == idSession::LOADING ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Denying party connection from %s because we were already in a game\n", peerAddress.ToString() );
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if( !CheckVersion( msg, peerAddress ) )
|
|
{
|
|
idLib::Printf( "NET: Denying user %s with wrong version number\n", peerAddress.ToString() );
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
|
|
idPacketProcessor::sessionId_t sessionID = msg.ReadUShort();
|
|
|
|
// Check to see if this is a peer trying to connect with a different sessionID
|
|
// If the peer got abruptly disconnected, the peer could be trying to reconnect from a non clean disconnect
|
|
if( peerNum >= 0 )
|
|
{
|
|
peer_t& existingPeer = peers[peerNum];
|
|
|
|
assert( existingPeer.GetConnectionState() != CONNECTION_FREE );
|
|
|
|
if( existingPeer.sessionID == sessionID )
|
|
{
|
|
return peerNum; // If this is the same sessionID, then assume redundant connection attempt
|
|
}
|
|
|
|
//
|
|
// This peer must be trying to reconnect from a previous abrupt disconnect
|
|
//
|
|
|
|
NET_VERBOSE_PRINT( "NET: Reconnecting peer %s for session %s\n", peerAddress.ToString(), GetLobbyName() );
|
|
|
|
// Assume a peer is trying to reconnect from a non clean disconnect
|
|
// We want to set the connection back to FREE manually, so we don't send a goodbye
|
|
existingPeer.connectionState = CONNECTION_FREE;
|
|
|
|
if( existingPeer.packetProc != NULL )
|
|
{
|
|
delete existingPeer.packetProc;
|
|
existingPeer.packetProc = NULL;
|
|
}
|
|
|
|
if( existingPeer.snapProc != NULL )
|
|
{
|
|
assert( lobbyType == TYPE_GAME ); // Only games sessions should be creating snap processors
|
|
delete existingPeer.snapProc;
|
|
existingPeer.snapProc = NULL;
|
|
}
|
|
|
|
RemoveUsersWithDisconnectedPeers();
|
|
|
|
peerNum = -1;
|
|
}
|
|
|
|
// See if this was from an invite we sent out. If it wasn't, make sure we aren't invite only
|
|
const bool fromInvite = msg.ReadBool();
|
|
|
|
if( !fromInvite && MatchTypeInviteOnly( parms.matchFlags ) )
|
|
{
|
|
idLib::Printf( "NET: Denying user %s because they were not invited to an invite only match\n", peerAddress.ToString() );
|
|
SendGoodbye( peerAddress );
|
|
return -1;
|
|
}
|
|
|
|
// Make sure we have room for the users connecting
|
|
if( !VerifyNumConnectingUsers( msg ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to being out of user slots\n", peerAddress.ToString(), GetLobbyName() );
|
|
SendGoodbye( peerAddress, true );
|
|
return -1;
|
|
}
|
|
|
|
// Make sure there are no lobby id conflicts
|
|
if( !verify( VerifyLobbyUserIDs( msg ) ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to lobby id conflict\n", peerAddress.ToString(), GetLobbyName() );
|
|
SendGoodbye( peerAddress, true );
|
|
return -1;
|
|
}
|
|
|
|
// Calling AddPeer will set our connectionState to this peer as CONNECTION_CONNECTING (which will get set to CONNECTION_ESTABLISHED below)
|
|
peerNum = AddPeer( peerAddress, sessionID );
|
|
|
|
peer_t& newPeer = peers[peerNum];
|
|
|
|
assert( newPeer.GetConnectionState() == CONNECTION_CONNECTING );
|
|
assert( lobbyType != GetActingGameStateLobbyType() || newPeer.snapProc != NULL );
|
|
|
|
// First, add users from this new peer to our user list
|
|
// (which will then forward the list to all peers except peerNum)
|
|
AddUsersFromMsg( msg, peerNum );
|
|
|
|
// Mark the peer as connected for this session type
|
|
SetPeerConnectionState( peerNum, CONNECTION_ESTABLISHED );
|
|
|
|
// Update their heart beat to current
|
|
newPeer.lastHeartBeat = Sys_Milliseconds();
|
|
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
|
|
idBitMsg outmsg( buffer, sizeof( buffer ) );
|
|
|
|
// Let them know their peer index on this host
|
|
// peerIndexOnHost (put this here so it shows up in search results when finding out where it's used/referenced)
|
|
outmsg.WriteLong( peerNum );
|
|
|
|
// If they are connecting to our party lobby, let them know the party token
|
|
if( lobbyType == TYPE_PARTY )
|
|
{
|
|
outmsg.WriteLong( GetPartyTokenAsHost() );
|
|
}
|
|
|
|
if( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE )
|
|
{
|
|
// If this is a game session, reset the loading and ingame flags
|
|
newPeer.loaded = false;
|
|
newPeer.inGame = false;
|
|
}
|
|
|
|
// Write out current match parms
|
|
parms.Write( outmsg );
|
|
|
|
// Send list of existing users to this new peer
|
|
// (the users from the new peer will also be in this list, since we already called AddUsersFromMsg)
|
|
outmsg.WriteByte( GetNumLobbyUsers() );
|
|
|
|
for( int u = 0; u < GetNumLobbyUsers(); u++ )
|
|
{
|
|
GetLobbyUser( u )->WriteToMsg( outmsg );
|
|
}
|
|
|
|
lobbyBackend->FillMsgWithPostConnectInfo( outmsg );
|
|
|
|
NET_VERBOSE_PRINT( "NET: Sending response to %s, lobbyType %s, sessionID %i\n", peerAddress.ToString(), GetLobbyName(), sessionID );
|
|
|
|
QueueReliableMessage( peerNum, RELIABLE_HELLO, outmsg.GetReadData(), outmsg.GetSize() );
|
|
|
|
if( MatchTypeIsJoinInProgress( parms.matchFlags ) )
|
|
{
|
|
// If have an active game lobby, and someone joins our party, tell them to join our game
|
|
if( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY )
|
|
{
|
|
SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
|
|
}
|
|
|
|
// We are are ingame, then start the client loading immediately
|
|
if( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() >= idSession::LOADING )
|
|
{
|
|
idLib::Printf( "******* JOIN IN PROGRESS ********\n" );
|
|
if( sessionCB->GetState() == idSession::INGAME )
|
|
{
|
|
newPeer.pauseSnapshots = true; // Since this player joined in progress, let game dictate when to start sending snaps
|
|
}
|
|
QueueReliableMessage( peerNum, idLobby::RELIABLE_START_LOADING );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we are in a game lobby, and someone joins our party, tell them to join our game
|
|
if( lobbyType == TYPE_PARTY && sessionCB->GetState() == idSession::GAME_LOBBY )
|
|
{
|
|
SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
|
|
}
|
|
}
|
|
|
|
// Send mic status of the current lobby to applicable peers
|
|
SendPeersMicStatusToNewUsers( peerNum );
|
|
|
|
// If we made is this far, update the users migration game data index
|
|
for( int u = 0; u < GetNumLobbyUsers(); u++ )
|
|
{
|
|
if( GetLobbyUser( u )->peerIndex == peerNum )
|
|
{
|
|
GetLobbyUser( u )->migrationGameData = migrationGameData;
|
|
}
|
|
}
|
|
|
|
return peerNum;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::InitStateLobbyHost
|
|
========================
|
|
*/
|
|
void idLobby::InitStateLobbyHost()
|
|
{
|
|
assert( lobbyBackend != NULL );
|
|
|
|
// We will be the host
|
|
isHost = true;
|
|
|
|
if( net_headlessServer.GetBool() )
|
|
{
|
|
return; // Don't add any players to headless server
|
|
}
|
|
|
|
if( migrationInfo.state != MIGRATE_NONE )
|
|
{
|
|
migrationInfo.persistUntilGameEndsData.wasMigratedHost = true; // InitSessionUsersFromLocalUsers needs to know this
|
|
migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = false;
|
|
// migrationDlg = GDM_MIGRATING_WAITING;
|
|
}
|
|
|
|
// Initialize the initial user list for this lobby
|
|
InitSessionUsersFromLocalUsers( MatchTypeIsOnline( parms.matchFlags ) );
|
|
|
|
// Set the session's hostAddress to the local players' address.
|
|
const int myUserIndex = GetLobbyUserIndexByLocalUserHandle( sessionCB->GetSignInManager().GetMasterLocalUserHandle() );
|
|
if( myUserIndex != -1 )
|
|
{
|
|
hostAddress = GetLobbyUser( myUserIndex )->address;
|
|
}
|
|
|
|
// Since we are the host, we have to register our initial session users with the lobby
|
|
// All additional users will join through AddUsersFromMsg, and RegisterUser is handled in there from here on out.
|
|
// Peers will add users exclusively through AddUsersFromMsg.
|
|
for( int i = 0; i < GetNumLobbyUsers(); i++ )
|
|
{
|
|
lobbyUser_t* user = GetLobbyUser( i );
|
|
RegisterUser( user );
|
|
if( lobbyType == TYPE_PARTY )
|
|
{
|
|
user->partyToken = GetPartyTokenAsHost();
|
|
}
|
|
}
|
|
|
|
// Set the lobbies skill level
|
|
lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() );
|
|
|
|
// Make sure and register all the addresses of the invites we'll send out as the new host
|
|
if( migrationInfo.state != MIGRATE_NONE )
|
|
{
|
|
// Tell the session that we became the host, so the session mgr can adjust state if needed
|
|
sessionCB->BecameHost( *this );
|
|
|
|
// Register this address with this lobbyBackend
|
|
for( int i = 0; i < migrationInfo.invites.Num(); i++ )
|
|
{
|
|
lobbyBackend->RegisterAddress( migrationInfo.invites[i].address );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendMembersToLobby
|
|
========================
|
|
*/
|
|
void idLobby::SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t& connectInfo, bool waitForOtherMembers )
|
|
{
|
|
|
|
// It's not our job to send party members to a game if we aren't the party host
|
|
if( !IsHost() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Send the message to all connected peers
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
if( peers[ i ].IsConnected() )
|
|
{
|
|
SendPeerMembersToLobby( i, destLobbyType, connectInfo, waitForOtherMembers );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendMembersToLobby
|
|
========================
|
|
*/
|
|
void idLobby::SendMembersToLobby( idLobby& destLobby, bool waitForOtherMembers )
|
|
{
|
|
if( destLobby.lobbyBackend == NULL )
|
|
{
|
|
return; // We don't have a game lobbyBackend to get an address for
|
|
}
|
|
|
|
lobbyConnectInfo_t connectInfo = destLobby.lobbyBackend->GetConnectInfo();
|
|
|
|
SendMembersToLobby( destLobby.lobbyType, connectInfo, waitForOtherMembers );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendPeerMembersToLobby
|
|
Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
|
|
========================
|
|
*/
|
|
void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t& connectInfo, bool waitForOtherMembers )
|
|
{
|
|
// It's not our job to send party members to a game if we aren't the party host
|
|
if( !IsHost() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
assert( peerIndex >= 0 );
|
|
assert( peerIndex < peers.Num() );
|
|
peer_t& peer = peers[ peerIndex ];
|
|
|
|
NET_VERBOSE_PRINT( "NET: Sending peer %i (%s) to game lobby\n", peerIndex, peer.address.ToString() );
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
idLib::Warning( "NET: Can't send peer %i to game lobby: peer isn't in party", peerIndex );
|
|
return;
|
|
}
|
|
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
|
|
idBitMsg outmsg( buffer, sizeof( buffer ) );
|
|
|
|
// Have lobby fill out msg with connection info
|
|
connectInfo.WriteToMsg( outmsg );
|
|
|
|
outmsg.WriteByte( destLobbyType );
|
|
outmsg.WriteBool( waitForOtherMembers );
|
|
|
|
QueueReliableMessage( peerIndex, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, outmsg.GetReadData(), outmsg.GetSize() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendPeerMembersToLobby
|
|
|
|
Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
|
|
========================
|
|
*/
|
|
void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers )
|
|
{
|
|
idLobby* lobby = sessionCB->GetLobbyFromType( destLobbyType );
|
|
|
|
if( !verify( lobby != NULL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !verify( lobby->lobbyBackend != NULL ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
lobbyConnectInfo_t connectInfo = lobby->lobbyBackend->GetConnectInfo();
|
|
|
|
SendPeerMembersToLobby( peerIndex, destLobbyType, connectInfo, waitForOtherMembers );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::NotifyPartyOfLeavingGameLobby
|
|
========================
|
|
*/
|
|
void idLobby::NotifyPartyOfLeavingGameLobby()
|
|
{
|
|
if( lobbyType != TYPE_PARTY )
|
|
{
|
|
return; // We are not a party lobby
|
|
}
|
|
|
|
if( !IsHost() )
|
|
{
|
|
return; // We are not the host of a party lobby, we can't do this
|
|
}
|
|
|
|
if( !( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) )
|
|
{
|
|
return; // Options aren't set to notify party of leaving
|
|
}
|
|
|
|
// Tell our party to leave the game they are in
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
if( peers[ i ].IsConnected() )
|
|
{
|
|
QueueReliableMessage( i, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetPartyTokenAsHost
|
|
========================
|
|
*/
|
|
uint32 idLobby::GetPartyTokenAsHost()
|
|
{
|
|
assert( lobbyType == TYPE_PARTY );
|
|
assert( IsHost() );
|
|
|
|
if( partyToken == 0 )
|
|
{
|
|
// I don't know if this is mathematically sound, but it seems reasonable.
|
|
// Don't do this at app startup (i.e. in the constructor) or it will be a lot less random.
|
|
unsigned long seed = Sys_Milliseconds(); // time app has been running
|
|
idLocalUser* masterUser = session->GetSignInManager().GetMasterLocalUser();
|
|
if( masterUser != NULL )
|
|
{
|
|
seed += idStr::Hash( masterUser->GetGamerTag() );
|
|
}
|
|
partyToken = idRandom( seed ).RandomInt();
|
|
idLib::Printf( "NET: PartyToken is %u (seed = %u)\n", partyToken, seed );
|
|
}
|
|
return partyToken;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::EncodeSessionID
|
|
========================
|
|
*/
|
|
idPacketProcessor::sessionId_t idLobby::EncodeSessionID( uint32 key ) const
|
|
{
|
|
assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
|
|
const int numBits = sizeof( idPacketProcessor::sessionId_t ) * 8 - idPacketProcessor::NUM_LOBBY_TYPE_BITS;
|
|
const uint32 mask = ( 1 << numBits ) - 1;
|
|
idPacketProcessor::sessionId_t sessionID = ( key & mask ) << idPacketProcessor::NUM_LOBBY_TYPE_BITS;
|
|
sessionID |= ( lobbyType + 1 );
|
|
return sessionID;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::EncodeSessionID
|
|
========================
|
|
*/
|
|
void idLobby::DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32& key ) const
|
|
{
|
|
assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
|
|
key = sessionID >> idPacketProcessor::NUM_LOBBY_TYPE_BITS;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GenerateSessionID
|
|
========================
|
|
*/
|
|
idPacketProcessor::sessionId_t idLobby::GenerateSessionID() const
|
|
{
|
|
idPacketProcessor::sessionId_t sessionID = EncodeSessionID( Sys_Milliseconds() );
|
|
|
|
// Make sure we can use it
|
|
while( !SessionIDCanBeUsedForInBand( sessionID ) )
|
|
{
|
|
sessionID = IncrementSessionID( sessionID );
|
|
}
|
|
|
|
return sessionID;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SessionIDCanBeUsedForInBand
|
|
========================
|
|
*/
|
|
bool idLobby::SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const
|
|
{
|
|
if( sessionID == idPacketProcessor::SESSION_ID_INVALID )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::IncrementSessionID
|
|
========================
|
|
*/
|
|
idPacketProcessor::sessionId_t idLobby::IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const
|
|
{
|
|
// Increment, taking into account valid id's
|
|
while( 1 )
|
|
{
|
|
uint32 key = 0;
|
|
|
|
DecodeSessionID( sessionID, key );
|
|
|
|
key++;
|
|
|
|
sessionID = EncodeSessionID( key );
|
|
|
|
if( SessionIDCanBeUsedForInBand( sessionID ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return sessionID;
|
|
}
|
|
|
|
#define VERIFY_CONNECTED_PEER( p, sessionType_, msgType ) \
|
|
if ( !verify( lobbyType == sessionType_ ) ) { \
|
|
idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
|
|
return; \
|
|
} \
|
|
if ( peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) { \
|
|
idLib::Printf( "NET: " #msgType ", peer:%s not connected for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
|
|
return; \
|
|
}
|
|
|
|
#define VERIFY_CONNECTING_PEER( p, sessionType_, msgType ) \
|
|
if ( !verify( lobbyType == sessionType_ ) ) { \
|
|
idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
|
|
return; \
|
|
} \
|
|
if ( peers[p].GetConnectionState() != CONNECTION_CONNECTING ) { \
|
|
idLib::Printf( "NET: " #msgType ", peer:%s not connecting for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
|
|
return; \
|
|
}
|
|
|
|
#define VERIFY_FROM_HOST( p, sessionType_, msgType ) \
|
|
VERIFY_CONNECTED_PEER( p, sessionType_, msgType ); \
|
|
if ( p != host ) { \
|
|
idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \
|
|
return; \
|
|
} \
|
|
|
|
#define VERIFY_FROM_CONNECTING_HOST( p, sessionType_, msgType ) \
|
|
VERIFY_CONNECTING_PEER( p, sessionType_, msgType ); \
|
|
if ( p != host ) { \
|
|
idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \
|
|
return; \
|
|
} \
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleHelloAck
|
|
========================
|
|
*/
|
|
void idLobby::HandleHelloAck( int p, idBitMsg& msg )
|
|
{
|
|
peer_t& peer = peers[p];
|
|
|
|
if( state != STATE_CONNECT_HELLO_WAIT )
|
|
{
|
|
idLib::Printf( "NET: Hello ack for session type %s while not waiting for hello.\n", GetLobbyName() );
|
|
SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore
|
|
return;
|
|
}
|
|
if( p != host )
|
|
{
|
|
// This shouldn't be possible
|
|
idLib::Printf( "NET: Hello ack for session type %s, not from correct host.\n", GetLobbyName() );
|
|
SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore
|
|
return;
|
|
}
|
|
|
|
assert( GetNumLobbyUsers() == 0 );
|
|
|
|
NET_VERBOSE_PRINT( "NET: Hello ack for session type %s from %s\n", GetLobbyName(), peer.address.ToString() );
|
|
|
|
// We are now connected to this session type
|
|
SetPeerConnectionState( p, CONNECTION_ESTABLISHED );
|
|
|
|
// Obtain what our peer index is on the host is
|
|
peerIndexOnHost = msg.ReadLong();
|
|
|
|
// If we connected to a party lobby, get the party token from the lobby owner
|
|
if( lobbyType == TYPE_PARTY )
|
|
{
|
|
partyToken = msg.ReadLong();
|
|
}
|
|
|
|
// Read match parms
|
|
parms.Read( msg );
|
|
|
|
// Update lobbyBackend with parms
|
|
if( lobbyBackend != NULL )
|
|
{
|
|
lobbyBackend->UpdateMatchParms( parms );
|
|
}
|
|
|
|
// Populate the user list with the one from the host (which will also include our local users)
|
|
// This ensures the user lists are kept in sync
|
|
FreeAllUsers();
|
|
AddUsersFromMsg( msg, p );
|
|
|
|
// Make sure the host has a current heartbeat
|
|
peer.lastHeartBeat = Sys_Milliseconds();
|
|
|
|
lobbyBackend->PostConnectFromMsg( msg );
|
|
|
|
// Tell the lobby controller to finalize the connection
|
|
SetState( STATE_FINALIZE_CONNECT );
|
|
|
|
//
|
|
// Success - We've received an ack from the server, letting us know we've been registered with the lobbies
|
|
//
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserName
|
|
========================
|
|
*/
|
|
const char* idLobby::GetLobbyUserName( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int index = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( index );
|
|
|
|
if( user == NULL )
|
|
{
|
|
for( int i = 0; i < disconnectedUsers.Num(); i++ )
|
|
{
|
|
if( disconnectedUsers[i].lobbyUserID.CompareIgnoreLobbyType( lobbyUserID ) )
|
|
{
|
|
return disconnectedUsers[i].gamertag;
|
|
}
|
|
}
|
|
return INVALID_LOBBY_USER_NAME;
|
|
}
|
|
|
|
return user->gamertag;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserSkinIndex
|
|
========================
|
|
*/
|
|
int idLobby::GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
return user ? user->selectedSkin : 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserWeaponAutoSwitch
|
|
========================
|
|
*/
|
|
bool idLobby::GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
return user ? user->weaponAutoSwitch : true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserWeaponAutoReload
|
|
========================
|
|
*/
|
|
bool idLobby::GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
return user ? user->weaponAutoReload : true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserLevel
|
|
========================
|
|
*/
|
|
int idLobby::GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
return user ? user->level : 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserQoS
|
|
========================
|
|
*/
|
|
int idLobby::GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
|
|
if( IsHost() && IsSessionUserIndexLocal( userIndex ) )
|
|
{
|
|
return 0; // Local users on the host of the active session have 0 ping
|
|
}
|
|
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
|
|
if( !verify( user != NULL ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return user->pingMs;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserTeam
|
|
========================
|
|
*/
|
|
int idLobby::GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
return user ? user->teamNumber : 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SetLobbyUserTeam
|
|
========================
|
|
*/
|
|
bool idLobby::SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber )
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
|
|
if( user != NULL )
|
|
{
|
|
if( teamNumber != user->teamNumber )
|
|
{
|
|
user->teamNumber = teamNumber;
|
|
if( IsHost() )
|
|
{
|
|
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
|
|
idBitMsg msg( buffer, sizeof( buffer ) );
|
|
CreateUserUpdateMessage( userIndex, msg );
|
|
idBitMsg readMsg;
|
|
readMsg.InitRead( buffer, msg.GetSize() );
|
|
UpdateSessionUserOnPeers( readMsg );
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLobbyUserPartyToken
|
|
========================
|
|
*/
|
|
int idLobby::GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
const lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
return user ? user->partyToken : 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetProfileFromLobbyUser
|
|
========================
|
|
*/
|
|
idPlayerProfile* idLobby::GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID )
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
|
|
idPlayerProfile* profile = NULL;
|
|
|
|
idLocalUser* localUser = GetLocalUserFromLobbyUserIndex( userIndex );
|
|
|
|
if( localUser != NULL )
|
|
{
|
|
profile = localUser->GetProfile();
|
|
}
|
|
|
|
if( profile == NULL )
|
|
{
|
|
// Whoops
|
|
profile = session->GetSignInManager().GetDefaultProfile();
|
|
//idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." );
|
|
}
|
|
|
|
return profile;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetLocalUserFromLobbyUser
|
|
========================
|
|
*/
|
|
idLocalUser* idLobby::GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID )
|
|
{
|
|
const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
|
|
return GetLocalUserFromLobbyUserIndex( userIndex );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetNumLobbyUsersOnTeam
|
|
========================
|
|
*/
|
|
int idLobby::GetNumLobbyUsersOnTeam( int teamNumber ) const
|
|
{
|
|
int numTeam = 0;
|
|
for( int i = 0; i < GetNumLobbyUsers(); ++i )
|
|
{
|
|
if( GetLobbyUser( i )->teamNumber == teamNumber )
|
|
{
|
|
++numTeam;
|
|
}
|
|
}
|
|
return numTeam;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetPeerName
|
|
========================
|
|
*/
|
|
const char* idLobby::GetPeerName( int peerNum ) const
|
|
{
|
|
|
|
for( int i = 0; i < GetNumLobbyUsers(); ++i )
|
|
{
|
|
if( !verify( GetLobbyUser( i ) != NULL ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( GetLobbyUser( i )->peerIndex == peerNum )
|
|
{
|
|
return GetLobbyUserName( GetLobbyUser( i )->lobbyUserID );
|
|
}
|
|
}
|
|
|
|
return INVALID_LOBBY_USER_NAME;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleReliableMsg
|
|
========================
|
|
*/
|
|
void idLobby::HandleReliableMsg( int p, idBitMsg& msg )
|
|
{
|
|
peer_t& peer = peers[p];
|
|
|
|
int reliableType = msg.ReadByte();
|
|
|
|
//idLib::Printf(" Received reliable msg: %i \n", reliableType );
|
|
|
|
const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
|
|
|
|
if( reliableType == RELIABLE_HELLO )
|
|
{
|
|
VERIFY_FROM_CONNECTING_HOST( p, lobbyType, RELIABLE_HELLO );
|
|
// This is sent from the host acking a request to join the game lobby
|
|
HandleHelloAck( p, msg );
|
|
return;
|
|
}
|
|
else if( reliableType == RELIABLE_USER_CONNECT_REQUEST )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_USER_CONNECT_REQUEST );
|
|
|
|
// This message is sent from a peer requesting for a new user to join the game lobby
|
|
// This will be sent while we are in a game lobby as a host. otherwise, denied.
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECT_REQUEST (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
|
|
|
|
idSession::sessionState_t expectedState = ( lobbyType == TYPE_PARTY ) ? idSession::PARTY_LOBBY : idSession::GAME_LOBBY;
|
|
|
|
if( sessionCB->GetState() == expectedState && IsHost() && NumFreeSlots() > 0 ) // This assumes only one user in the msg
|
|
{
|
|
// Add user to session, which will also forward the operation to all other peers
|
|
AddUsersFromMsg( msg, p );
|
|
}
|
|
else
|
|
{
|
|
// Let peer know user couldn't be added
|
|
HandleUserConnectFailure( p, msg, RELIABLE_USER_CONNECT_DENIED );
|
|
}
|
|
}
|
|
else if( reliableType == RELIABLE_USER_CONNECT_DENIED )
|
|
{
|
|
// This message is sent back from the host when a RELIABLE_PARTY_USER_CONNECT_REQUEST failed
|
|
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_PARTY_USER_CONNECT_DENIED );
|
|
|
|
// Remove this user from the sign-in manager, so we don't keep trying to add them
|
|
if( !sessionCB->GetSignInManager().RemoveLocalUserByHandle( localUserHandle_t( msg.ReadLong() ) ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_USER_CONNECT_DENIED, local user not found\n" );
|
|
return;
|
|
}
|
|
}
|
|
else if( reliableType == RELIABLE_KICK_PLAYER )
|
|
{
|
|
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_KICK_PLAYER );
|
|
common->Dialog().AddDialog( GDM_KICKED, DIALOG_ACCEPT, NULL, NULL, false );
|
|
if( sessionCB->GetPartyLobby().IsHost() )
|
|
{
|
|
session->SetSessionOption( idSession::OPTION_LEAVE_WITH_PARTY );
|
|
}
|
|
session->Cancel();
|
|
}
|
|
else if( reliableType == RELIABLE_HEADSET_STATE )
|
|
{
|
|
HandleHeadsetStateChange( p, msg );
|
|
}
|
|
else if( reliableType == RELIABLE_USER_CONNECTED )
|
|
{
|
|
// This message is sent back from the host when users have connected, and we need to update our lists to reflect that
|
|
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_CONNECTED );
|
|
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECTED (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
|
|
AddUsersFromMsg( msg, p );
|
|
}
|
|
else if( reliableType == RELIABLE_USER_DISCONNECTED )
|
|
{
|
|
// This message is sent back from the host when users have diconnected, and we need to update our lists to reflect that
|
|
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_DISCONNECTED );
|
|
|
|
ProcessUserDisconnectMsg( msg );
|
|
}
|
|
else if( reliableType == RELIABLE_MATCH_PARMS )
|
|
{
|
|
parms.Read( msg );
|
|
// Update lobby with parms
|
|
if( lobbyBackend != NULL )
|
|
{
|
|
lobbyBackend->UpdateMatchParms( parms );
|
|
}
|
|
}
|
|
else if( reliableType == RELIABLE_START_LOADING )
|
|
{
|
|
// This message is sent from the host to start loading a map
|
|
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_START_LOADING );
|
|
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_START_LOADING from %s\n", peer.address.ToString() );
|
|
|
|
startLoadingFromHost = true;
|
|
}
|
|
else if( reliableType == RELIABLE_LOADING_DONE )
|
|
{
|
|
// This message is sent from the peers to state they are done loading the map
|
|
VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_LOADING_DONE );
|
|
|
|
unsigned long networkChecksum = 0;
|
|
networkChecksum = msg.ReadLong();
|
|
|
|
peer.networkChecksum = networkChecksum;
|
|
peer.loaded = true;
|
|
}
|
|
else if( reliableType == RELIABLE_IN_GAME )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_IN_GAME );
|
|
|
|
peer.inGame = true;
|
|
}
|
|
else if( reliableType == RELIABLE_SNAPSHOT_ACK )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_SNAPSHOT_ACK );
|
|
|
|
// update our base state for his last received snapshot
|
|
int snapNum = msg.ReadLong();
|
|
float receivedBps = msg.ReadQuantizedUFloat< BANDWIDTH_REPORTING_MAX, BANDWIDTH_REPORTING_BITS >();
|
|
|
|
// Update reported received bps
|
|
if( peer.receivedBpsIndex != snapNum )
|
|
{
|
|
// Only do this the first time we get reported bps per snapshot. Subsequent ACKs of the same shot will usually have lower reported bps
|
|
// due to more time elapsing but not receiving a new ss
|
|
peer.receivedBps = receivedBps;
|
|
peer.receivedBpsIndex = snapNum;
|
|
}
|
|
|
|
ApplySnapshotDelta( p, snapNum );
|
|
|
|
//idLib::Printf( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum );
|
|
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ) );
|
|
|
|
}
|
|
else if( reliableType == RELIABLE_RESOURCE_ACK )
|
|
{
|
|
}
|
|
else if( reliableType == RELIABLE_UPDATE_MATCH_PARMS )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_UPDATE_MATCH_PARMS );
|
|
int msgType = msg.ReadLong();
|
|
sessionCB->HandlePeerMatchParamUpdate( p, msgType );
|
|
|
|
}
|
|
else if( reliableType == RELIABLE_MATCHFINISHED )
|
|
{
|
|
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_MATCHFINISHED );
|
|
|
|
sessionCB->ClearMigrationState();
|
|
|
|
}
|
|
else if( reliableType == RELIABLE_ENDMATCH )
|
|
{
|
|
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH );
|
|
|
|
sessionCB->EndMatchInternal();
|
|
|
|
}
|
|
else if( reliableType == RELIABLE_ENDMATCH_PREMATURE )
|
|
{
|
|
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH_PREMATURE );
|
|
|
|
sessionCB->EndMatchInternal( true );
|
|
|
|
}
|
|
else if( reliableType == RELIABLE_START_MATCH_GAME_LOBBY_HOST )
|
|
{
|
|
// This message should be from the host of the game lobby, telling us (as the host of the GameStateLobby) to start loading
|
|
VERIFY_CONNECTED_PEER( p, TYPE_GAME_STATE, RELIABLE_START_MATCH_GAME_LOBBY_HOST );
|
|
|
|
if( session->GetState() >= idSession::LOADING )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_START_MATCH_GAME_LOBBY_HOST already loading\n" );
|
|
return;
|
|
}
|
|
|
|
// Read match parms, and start loading
|
|
parms.Read( msg );
|
|
|
|
// Send these new match parms to currently connected peers
|
|
SendMatchParmsToPeers();
|
|
|
|
startLoadingFromHost = true; // Hijack this flag
|
|
}
|
|
else if( reliableType == RELIABLE_ARBITRATE )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE );
|
|
// Host telling us to arbitrate
|
|
// Set a flag to do this later, since the lobby may not be in a state where it can fulfil the request at the moment
|
|
respondToArbitrate = true;
|
|
}
|
|
else if( reliableType == RELIABLE_ARBITRATE_OK )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE_OK );
|
|
|
|
NET_VERBOSE_PRINT( "NET: Got an arbitration ok from %d\n", p );
|
|
|
|
everyoneArbitrated = true;
|
|
for( int i = 0; i < GetNumLobbyUsers(); i++ )
|
|
{
|
|
lobbyUser_t* user = GetLobbyUser( i );
|
|
if( !verify( user != NULL ) )
|
|
{
|
|
continue;
|
|
}
|
|
if( user->peerIndex == p )
|
|
{
|
|
user->arbitrationAcked = true;
|
|
}
|
|
else if( !user->arbitrationAcked )
|
|
{
|
|
everyoneArbitrated = false;
|
|
}
|
|
}
|
|
|
|
if( everyoneArbitrated )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Everyone says they registered for arbitration, verifying\n" );
|
|
lobbyBackend->Arbitrate();
|
|
//sessionCB->EveryoneArbitrated();
|
|
return;
|
|
}
|
|
}
|
|
else if( reliableType == RELIABLE_POST_STATS )
|
|
{
|
|
VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_POST_STATS );
|
|
sessionCB->RecvLeaderboardStats( msg );
|
|
}
|
|
else if( reliableType == RELIABLE_SESSION_USER_MODIFIED )
|
|
{
|
|
VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_SESSION_USER_MODIFIED );
|
|
UpdateSessionUserOnPeers( msg );
|
|
|
|
}
|
|
else if( reliableType == RELIABLE_UPDATE_SESSION_USER )
|
|
{
|
|
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_UPDATE_SESSION_USER );
|
|
HandleUpdateSessionUser( msg );
|
|
}
|
|
else if( reliableType == RELIABLE_CONNECT_AND_MOVE_TO_LOBBY )
|
|
{
|
|
VERIFY_FROM_HOST( p, lobbyType, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY );
|
|
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_CONNECT_AND_MOVE_TO_LOBBY\n" );
|
|
|
|
if( IsHost() )
|
|
{
|
|
idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: We are the host.\n" );
|
|
return;
|
|
}
|
|
|
|
// Get connection info
|
|
lobbyConnectInfo_t connectInfo;
|
|
connectInfo.ReadFromMsg( msg );
|
|
|
|
const lobbyType_t destLobbyType = ( lobbyType_t )msg.ReadByte();
|
|
const bool waitForMembers = msg.ReadBool();
|
|
|
|
assert( destLobbyType > lobbyType ); // Make sure this is a proper transition (i.e. TYPE_PARTY moves to TYPE_GAME, TYPE_GAME moves to TYPE_GAME_STATE)
|
|
|
|
sessionCB->ConnectAndMoveToLobby( destLobbyType, connectInfo, waitForMembers );
|
|
}
|
|
else if( reliableType == RELIABLE_PARTY_CONNECT_OK )
|
|
{
|
|
VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_CONNECT_OK );
|
|
if( !sessionCB->GetGameLobby().waitForPartyOk )
|
|
{
|
|
idLib::Printf( "RELIABLE_PARTY_CONNECT_OK: Wasn't waiting for ok.\n" );
|
|
}
|
|
sessionCB->GetGameLobby().waitForPartyOk = false;
|
|
}
|
|
else if( reliableType == RELIABLE_PARTY_LEAVE_GAME_LOBBY )
|
|
{
|
|
VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
|
|
|
|
NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_LEAVE_GAME_LOBBY\n" );
|
|
|
|
if( sessionCB->GetState() != idSession::GAME_LOBBY )
|
|
{
|
|
idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Not in a game lobby, ignoring.\n" );
|
|
return;
|
|
}
|
|
|
|
if( IsHost() )
|
|
{
|
|
idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Host of party, ignoring.\n" );
|
|
return;
|
|
}
|
|
|
|
sessionCB->LeaveGameLobby();
|
|
}
|
|
else if( IsReliablePlayerToPlayerType( reliableType ) )
|
|
{
|
|
HandleReliablePlayerToPlayerMsg( p, msg, reliableType );
|
|
}
|
|
else if( reliableType == RELIABLE_PING )
|
|
{
|
|
HandleReliablePing( p, msg );
|
|
}
|
|
else if( reliableType == RELIABLE_PING_VALUES )
|
|
{
|
|
HandlePingValues( msg );
|
|
}
|
|
else if( reliableType == RELIABLE_BANDWIDTH_VALUES )
|
|
{
|
|
HandleBandwidhTestValue( p, msg );
|
|
}
|
|
else if( reliableType == RELIABLE_MIGRATION_GAME_DATA )
|
|
{
|
|
HandleMigrationGameData( msg );
|
|
}
|
|
else if( reliableType >= RELIABLE_GAME_DATA )
|
|
{
|
|
|
|
VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_GAME_DATA );
|
|
|
|
common->NetReceiveReliable( p, reliableType - RELIABLE_GAME_DATA, msg );
|
|
}
|
|
else if( reliableType == RELIABLE_DUMMY_MSG )
|
|
{
|
|
// Ignore dummy msg's
|
|
NET_VERBOSE_PRINT( "NET: ignoring dummy msg from %s\n", peer.address.ToString() );
|
|
}
|
|
else
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Unknown reliable packet type %d from %s\n", reliableType, peer.address.ToString() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetTotalOutgoingRate
|
|
========================
|
|
*/
|
|
int idLobby::GetTotalOutgoingRate()
|
|
{
|
|
int totalSendRate = 0;
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
const peer_t& peer = peers[p];
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const idPacketProcessor& proc = *peer.packetProc;
|
|
|
|
totalSendRate += proc.GetOutgoingRateBytes();
|
|
}
|
|
return totalSendRate;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::DrawDebugNetworkHUD
|
|
========================
|
|
*/
|
|
extern idCVar net_forceUpstream;
|
|
void idLobby::DrawDebugNetworkHUD() const
|
|
{
|
|
int totalSendRate = 0;
|
|
int totalRecvRate = 0;
|
|
float totalSentMB = 0.0f;
|
|
float totalRecvMB = 0.0f;
|
|
|
|
const float Y_OFFSET = 20.0f;
|
|
const float X_OFFSET = 20.0f;
|
|
const float Y_SPACING = 15.0f;
|
|
|
|
float curY = Y_OFFSET;
|
|
|
|
int numLines = ( net_forceUpstream.GetFloat() != 0.0f ? 6 : 5 );
|
|
|
|
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 1550, ( peers.Num() + numLines ) * Y_SPACING + 20.0f );
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "# Peer | Sent kB/s | Recv kB/s | Sent MB | Recv MB | Ping | L | % | R.NM | R.SZ | R.AK | T", colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
const peer_t& peer = peers[p];
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const idPacketProcessor& proc = *peer.packetProc;
|
|
|
|
totalSendRate += proc.GetOutgoingRateBytes();
|
|
totalRecvRate += proc.GetIncomingRateBytes();
|
|
float sentKps = ( float )proc.GetOutgoingRateBytes() / 1024.0f;
|
|
float recvKps = ( float )proc.GetIncomingRateBytes() / 1024.0f;
|
|
float sentMB = ( float )proc.GetOutgoingBytes() / ( 1024.0f * 1024.0f );
|
|
float recvMB = ( float )proc.GetIncomingBytes() / ( 1024.0f * 1024.0f );
|
|
|
|
totalSentMB += sentMB;
|
|
totalRecvMB += recvMB;
|
|
|
|
idVec4 color = sentKps > 20.0f ? colorRed : colorGreen;
|
|
|
|
int resourcePercent = 0;
|
|
idStr name = peer.address.ToString();
|
|
|
|
name += lobbyType == TYPE_PARTY ? "(P" : "(G";
|
|
name += host == p ? ":H)" : ":C)";
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i %22s | %2.02f kB/s | %2.02f kB/s | %2.02f MB | %2.02f MB |%4i ms | %i | %i%% | %i | %i | %i | %2.2f / %2.2f / %i", p, name.c_str(), sentKps, recvKps, sentMB, recvMB, peer.lastPingRtt, peer.loaded, resourcePercent, peer.packetProc->NumQueuedReliables(), peer.packetProc->GetReliableDataSize(), peer.packetProc->NeedToSendReliableAck(), peer.snapHz, peer.maxSnapBps, peer.failedPingRecoveries ), color, false );
|
|
curY += Y_SPACING;
|
|
}
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
float totalSentKps = ( float )totalSendRate / 1024.0f;
|
|
float totalRecvKps = ( float )totalRecvRate / 1024.0f;
|
|
|
|
idVec4 color = totalSentKps > 100.0f ? colorRed : colorGreen;
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "# %20s | %2.02f KB/s | %2.02f KB/s | %2.02f MB | %2.02f MB", "", totalSentKps, totalRecvKps, totalSentMB, totalRecvMB ), color, false );
|
|
curY += Y_SPACING;
|
|
|
|
if( net_forceUpstream.GetFloat() != 0.0f )
|
|
{
|
|
float upstreamDropRate = session->GetUpstreamDropRate();
|
|
float upstreamQueuedRate = session->GetUpstreamQueueRate();
|
|
|
|
int queuedBytes = session->GetQueuedBytes();
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s -> Effective %2.02f kB/s", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f, totalSentKps - ( upstreamDropRate / 1024.0f ) + ( upstreamQueuedRate / 1024.0f ) ), color, false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::DrawDebugNetworkHUD2
|
|
========================
|
|
*/
|
|
void idLobby::DrawDebugNetworkHUD2() const
|
|
{
|
|
int totalSendRate = 0;
|
|
int totalRecvRate = 0;
|
|
|
|
const float Y_OFFSET = 20.0f;
|
|
const float X_OFFSET = 20.0f;
|
|
const float Y_SPACING = 15.0f;
|
|
|
|
float curY = Y_OFFSET;
|
|
|
|
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, ( peers.Num() + 4 ) * Y_SPACING + 20.0f );
|
|
|
|
const char* stateName = session->GetStateString();
|
|
|
|
renderSystem->DrawFilled( idVec4( 1.0f, 1.0f, 1.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, ( peers.Num() + 5 ) * Y_SPACING + 20.0f );
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), va( "State: %s. Local time: %d", stateName, Sys_Milliseconds() ), colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "Peer | Sent kB/s | Recv kB/s | L | R | Resources", colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
|
|
if( !peers[ p ].IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
idPacketProcessor& proc = *peers[ p ].packetProc;
|
|
|
|
totalSendRate += proc.GetOutgoingRate2();
|
|
totalRecvRate += proc.GetIncomingRate2();
|
|
float sentKps = ( float )proc.GetOutgoingRate2() / 1024.0f;
|
|
float recvKps = ( float )proc.GetIncomingRate2() / 1024.0f;
|
|
|
|
// should probably complement that with a bandwidth reading
|
|
// right now I am mostly concerned about fragmentation and the latency spikes it will cause
|
|
idVec4 color = proc.TickFragmentAccumulator() ? colorRed : colorGreen;
|
|
|
|
int rLoaded = peers[ p ].numResources;
|
|
int rTotal = 0;
|
|
|
|
// show the names of the clients connected to the server. Also make sure it looks reasonably good.
|
|
idStr peerName;
|
|
if( IsHost() )
|
|
{
|
|
peerName = GetPeerName( p );
|
|
|
|
int MAX_PEERNAME_LENGTH = 10;
|
|
int nameLength = peerName.Length();
|
|
if( nameLength > MAX_PEERNAME_LENGTH )
|
|
{
|
|
peerName = peerName.Left( MAX_PEERNAME_LENGTH );
|
|
}
|
|
else if( nameLength < MAX_PEERNAME_LENGTH )
|
|
{
|
|
idStr filler;
|
|
filler.Fill( ' ', MAX_PEERNAME_LENGTH );
|
|
peerName += filler.Left( MAX_PEERNAME_LENGTH - nameLength );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
peerName = "Local ";
|
|
}
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i - %s | %2.02f kB/s | %2.02f kB/s | %i | %i | %d/%d", p, peerName.c_str(), sentKps, recvKps, peers[p].loaded, peers[p].address.UsingRelay(), rLoaded, rTotal ), color, false );
|
|
curY += Y_SPACING;
|
|
}
|
|
|
|
renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
|
|
curY += Y_SPACING;
|
|
|
|
float totalSentKps = ( float )totalSendRate / 1024.0f;
|
|
float totalRecvKps = ( float )totalRecvRate / 1024.0f;
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Total | %2.02f KB/s | %2.02f KB/s", totalSentKps, totalRecvKps ), colorGreen, false );
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics
|
|
========================
|
|
*/
|
|
idCVar net_debughud3_bps_max( "net_debughud3_bps_max", "5120.0f", CVAR_FLOAT, "Highest factor of server base snapRate that a client can be throttled" );
|
|
void idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw )
|
|
{
|
|
const float Y_OFFSET = 20.0f;
|
|
const float X_OFFSET = 20.0f;
|
|
const float Y_SPACING = 15.0f;
|
|
idVec4 color = colorWhite;
|
|
|
|
float curY = Y_OFFSET;
|
|
|
|
if( !draw )
|
|
{
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
for( int i = 0; i < peers[p].debugGraphs.Num(); i++ )
|
|
{
|
|
if( peers[p].debugGraphs[i] != NULL )
|
|
{
|
|
peers[p].debugGraphs[i]->Enable( false );
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int lastTime = 0;
|
|
int time = Sys_Milliseconds();
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
idPacketProcessor* packetProc = peer.packetProc;
|
|
idSnapshotProcessor* snapProc = peer.snapProc;
|
|
|
|
if( !verify( packetProc != NULL && snapProc != NULL ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int snapSeq = snapProc->GetSnapSequence();
|
|
int snapBase = snapProc->GetBaseSequence();
|
|
int deltaSeq = snapSeq - snapBase;
|
|
bool throttled = peer.throttledSnapRate > common->GetSnapRate();
|
|
|
|
int numLines = net_forceUpstream.GetBool() ? 5 : 4;
|
|
|
|
const int width = renderSystem->GetWidth() / 2.0f - ( X_OFFSET * 2 );
|
|
|
|
enum netDebugGraphs_t
|
|
{
|
|
GRAPH_SNAPSENT,
|
|
GRAPH_OUTGOING,
|
|
GRAPH_INCOMINGREPORTED,
|
|
GRAPH_MAX
|
|
};
|
|
|
|
peer.debugGraphs.SetNum( GRAPH_MAX, NULL );
|
|
for( int i = 0; i < GRAPH_MAX; i++ )
|
|
{
|
|
// Initialize graphs
|
|
if( peer.debugGraphs[i] == NULL )
|
|
{
|
|
peer.debugGraphs[i] = console->CreateGraph( 500 );
|
|
if( !verify( peer.debugGraphs[i] != NULL ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
peer.debugGraphs[i]->SetPosition( X_OFFSET - 10.0f + width, curY - 10.0f, width , Y_SPACING * numLines );
|
|
}
|
|
|
|
peer.debugGraphs[i]->Enable( true );
|
|
}
|
|
|
|
renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, width, ( Y_SPACING * numLines ) + 20.0f );
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Peer %d - %s RTT %d %sPeerSnapRate: %d %s", p, GetPeerName( p ), peer.lastPingRtt, throttled ? "^1" : "^2", peer.throttledSnapRate / 1000, throttled ? "^1Throttled" : "" ), color, false );
|
|
curY += Y_SPACING;
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "SnapSeq %d BaseSeq %d Delta %d Queue %d", snapSeq, snapBase, deltaSeq, snapProc->GetSnapQueueSize() ), color, false );
|
|
curY += Y_SPACING;
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Reliables: %d / %d bytes Reliable Ack: %d", packetProc->NumQueuedReliables(), packetProc->GetReliableDataSize(), packetProc->NeedToSendReliableAck() ), color, false );
|
|
curY += Y_SPACING;
|
|
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Outgoing %.2f kB/s Reported %.2f kB/s Throttle: %.2f", peer.packetProc->GetOutgoingRateBytes() / 1024.0f, peers[p].receivedBps / 1024.0f, peer.receivedThrottle ), color, false );
|
|
curY += Y_SPACING;
|
|
|
|
if( net_forceUpstream.GetFloat() != 0.0f )
|
|
{
|
|
float upstreamDropRate = session->GetUpstreamDropRate();
|
|
float upstreamQueuedRate = session->GetUpstreamQueueRate();
|
|
int queuedBytes = session->GetQueuedBytes();
|
|
renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s ", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f ), color, false );
|
|
|
|
}
|
|
|
|
curY += Y_SPACING;
|
|
|
|
|
|
|
|
if( peer.debugGraphs[GRAPH_SNAPSENT] != NULL )
|
|
{
|
|
if( peer.lastSnapTime > lastTime )
|
|
{
|
|
peer.debugGraphs[GRAPH_SNAPSENT]->SetValue( -1, 1.0f, colorBlue );
|
|
}
|
|
else
|
|
{
|
|
peer.debugGraphs[GRAPH_SNAPSENT]->SetValue( -1, 0.0f, colorBlue );
|
|
}
|
|
}
|
|
|
|
if( peer.debugGraphs[GRAPH_OUTGOING] != NULL )
|
|
{
|
|
idVec4 bgColor( vec4_zero );
|
|
peer.debugGraphs[GRAPH_OUTGOING]->SetBackgroundColor( bgColor );
|
|
|
|
idVec4 lineColor = colorLtGrey;
|
|
lineColor.w = 0.5f;
|
|
float outgoingRate = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
|
|
// peer.packetProc->GetOutgoingRateBytes()
|
|
peer.debugGraphs[GRAPH_OUTGOING]->SetValue( -1, idMath::ClampFloat( 0.0f, 1.0f, outgoingRate / net_debughud3_bps_max.GetFloat() ), lineColor );
|
|
}
|
|
|
|
|
|
if( peer.debugGraphs[GRAPH_INCOMINGREPORTED] != NULL )
|
|
{
|
|
idVec4 lineColor = colorYellow;
|
|
extern idCVar net_peer_throttle_bps_peer_threshold_pct;
|
|
extern idCVar net_peer_throttle_bps_host_threshold;
|
|
|
|
if( peer.packetProc->GetOutgoingRateBytes() > net_peer_throttle_bps_host_threshold.GetFloat() )
|
|
{
|
|
float pct = peer.packetProc->GetOutgoingRateBytes() > 0.0f ? peer.receivedBps / peer.packetProc->GetOutgoingRateBytes() : 0.0f;
|
|
if( pct < net_peer_throttle_bps_peer_threshold_pct.GetFloat() )
|
|
{
|
|
lineColor = colorRed;
|
|
}
|
|
else
|
|
{
|
|
lineColor = colorGreen;
|
|
}
|
|
}
|
|
idVec4 bgColor( vec4_zero );
|
|
peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetBackgroundColor( bgColor );
|
|
peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetFillMode( idDebugGraph::GRAPH_LINE );
|
|
peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetValue( -1, idMath::ClampFloat( 0.0f, 1.0f, peer.receivedBps / net_debughud3_bps_max.GetFloat() ), lineColor );
|
|
}
|
|
|
|
|
|
|
|
// Skip down
|
|
curY += ( Y_SPACING * 2.0f );
|
|
}
|
|
|
|
lastTime = time;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::CheckHeartBeats
|
|
========================
|
|
*/
|
|
void idLobby::CheckHeartBeats()
|
|
{
|
|
// Disconnect peers that haven't responded within net_peerTimeoutInSeconds
|
|
int time = Sys_Milliseconds();
|
|
|
|
int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
|
|
|
|
if( sessionCB->GetState() < idSession::LOADING && migrationInfo.state == MIGRATE_NONE )
|
|
{
|
|
// Use shorter timeout in lobby (TCR)
|
|
timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds_Lobby", net_peerTimeoutInSeconds_Lobby.GetInteger() ) * 1000;
|
|
}
|
|
|
|
if( timeoutInMs > 0 )
|
|
{
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].IsConnected() )
|
|
{
|
|
|
|
bool peerTimeout = false;
|
|
|
|
if( time - peers[p].lastHeartBeat > timeoutInMs )
|
|
{
|
|
peerTimeout = true;
|
|
}
|
|
|
|
// if reliable queue is almost full, disconnect the peer.
|
|
// (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never
|
|
// have more than 3 or 4 queued)
|
|
if( peers[ p ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE - 1 )
|
|
{
|
|
peerTimeout = true;
|
|
}
|
|
|
|
if( peerTimeout )
|
|
{
|
|
// Disconnect the peer from any sessions we are a host of
|
|
if( IsHost() )
|
|
{
|
|
idLib::Printf( "Peer %i timed out for %s session @ %d (lastHeartBeat %d)\n", p, GetLobbyName(), time, peers[p].lastHeartBeat );
|
|
DisconnectPeerFromSession( p );
|
|
}
|
|
|
|
// Handle peers not receiving a heartbeat from the host in awhile
|
|
if( IsPeer() )
|
|
{
|
|
if( migrationInfo.state != MIGRATE_PICKING_HOST )
|
|
{
|
|
idLib::Printf( "Host timed out for %s session\n", GetLobbyName() );
|
|
|
|
// Pick a host for this session
|
|
PickNewHost();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( IsHost() && lobbyType == GetActingGameStateLobbyType() )
|
|
{
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( !peers[p].IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CheckPeerThrottle( p );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::CheckHeartBeats
|
|
========================
|
|
*/
|
|
bool idLobby::IsLosingConnectionToHost() const
|
|
{
|
|
if( !verify( IsPeer() && host >= 0 && host < peers.Num() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !peers[ host ].IsConnected() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
|
|
int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
|
|
|
|
// return true if heartbeat > half the timeout length
|
|
if( timeoutInMs > 0 && time - peers[ host ].lastHeartBeat > timeoutInMs / 2 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// return true if reliable queue is more than half full
|
|
// (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never
|
|
// have more than 3 or 4 queued)
|
|
if( peers[ host ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::IsMigratedStatsGame
|
|
========================
|
|
*/
|
|
bool idLobby::IsMigratedStatsGame() const
|
|
{
|
|
if( !IsLobbyActive() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( lobbyType != TYPE_GAME )
|
|
{
|
|
return false; // Only game session migrates games stats
|
|
}
|
|
|
|
if( !MatchTypeHasStats( parms.matchFlags ) )
|
|
{
|
|
return false; // Only stats games migrate stats
|
|
}
|
|
|
|
if( !MatchTypeIsRanked( parms.matchFlags ) )
|
|
{
|
|
return false; // Only ranked games should migrate stats into new game
|
|
}
|
|
|
|
return migrationInfo.persistUntilGameEndsData.wasMigratedGame && migrationInfo.persistUntilGameEndsData.hasGameData;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ShouldRelaunchMigrationGame
|
|
returns true if we are hosting a migrated game and we had valid migration data
|
|
========================
|
|
*/
|
|
bool idLobby::ShouldRelaunchMigrationGame() const
|
|
{
|
|
if( IsMigrating() )
|
|
{
|
|
return false; // Don't relaunch until all clients have reconnected
|
|
}
|
|
|
|
if( !IsMigratedStatsGame() )
|
|
{
|
|
return false; // If we are not migrating stats, we don't want to relaunch a new game
|
|
}
|
|
|
|
if( !migrationInfo.persistUntilGameEndsData.wasMigratedHost )
|
|
{
|
|
return false; // Only relaunch if we are the host
|
|
}
|
|
|
|
if( migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame )
|
|
{
|
|
return false; // We already relaunched this game
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ShouldShowMigratingDialog
|
|
========================
|
|
*/
|
|
bool idLobby::ShouldShowMigratingDialog() const
|
|
{
|
|
if( IsMigrating() )
|
|
{
|
|
return true; // If we are in the process of truly migrating, then definitely return true
|
|
}
|
|
|
|
if( sessionCB->GetState() == idSession::INGAME )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We're either waiting on the server (which could be us) to relaunch, so show the dialog
|
|
return IsMigratedStatsGame() && sessionCB->GetState() != idSession::INGAME;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::IsMigrating
|
|
========================
|
|
*/
|
|
bool idLobby::IsMigrating() const
|
|
{
|
|
return migrationInfo.state != idLobby::MIGRATE_NONE;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::PingPeers
|
|
Host only.
|
|
========================
|
|
*/
|
|
void idLobby::PingPeers()
|
|
{
|
|
if( !verify( IsHost() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int now = Sys_Milliseconds();
|
|
|
|
pktPing_t packet;
|
|
memset( &packet, 0, sizeof( packet ) ); // We're gonna memset like it's 1999.
|
|
packet.timestamp = now;
|
|
|
|
byte packetCopy[ sizeof( packet ) ];
|
|
idBitMsg msg( packetCopy, sizeof( packetCopy ) );
|
|
msg.WriteLong( packet.timestamp );
|
|
|
|
for( int i = 0; i < peers.Num(); ++i )
|
|
{
|
|
peer_t& peer = peers[ i ];
|
|
if( !peer.IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
if( peer.nextPing <= now )
|
|
{
|
|
peer.nextPing = now + PING_INTERVAL_MS;
|
|
QueueReliableMessage( i, RELIABLE_PING, msg.GetReadData(), msg.GetSize() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ThrottlePeerSnapRate
|
|
========================
|
|
*/
|
|
void idLobby::ThrottlePeerSnapRate( int p )
|
|
{
|
|
if( !verify( IsHost() ) || !verify( p >= 0 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
peers[p].throttledSnapRate = common->GetSnapRate() * 2;
|
|
idLib::Printf( "^1Throttling peer %d %s!\n", p, GetPeerName( p ) );
|
|
idLib::Printf( " New snaprate: %d\n", peers[p].throttledSnapRate / 1000 );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SaturatePeers
|
|
========================
|
|
*/
|
|
void idLobby::BeginBandwidthTest()
|
|
{
|
|
if( !verify( IsHost() ) )
|
|
{
|
|
idLib::Warning( "Bandwidth test should only be done on host" );
|
|
return;
|
|
}
|
|
|
|
if( bandwidthChallengeStartTime > 0 )
|
|
{
|
|
idLib::Warning( "Already started bandwidth test" );
|
|
return;
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
bandwidthChallengeStartTime = time;
|
|
bandwidthChallengeEndTime = 0;
|
|
bandwidthChallengeFinished = false;
|
|
bandwidthChallengeNumGoodSeq = 0;
|
|
|
|
for( int p = 0; p < peers.Num(); ++p )
|
|
{
|
|
if( !peers[ p ].IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( !verify( peers[ p ].packetProc != NULL ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
peers[ p ].bandwidthSequenceNum = 0;
|
|
peers[ p ].bandwidthChallengeStartSendTime = 0;
|
|
peers[ p ].bandwidthChallengeResults = false;
|
|
peers[ p ].bandwidthChallengeSendComplete = false;
|
|
peers[ p ].bandwidthTestBytes = peers[ p ].packetProc->GetOutgoingBytes(); // cache this off so we can see the difference when we are done
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SaturatePeers
|
|
========================
|
|
*/
|
|
bool idLobby::BandwidthTestStarted()
|
|
{
|
|
return bandwidthChallengeStartTime != 0;
|
|
}
|
|
/*
|
|
========================
|
|
idLobby::ServerUpdateBandwidthTest
|
|
========================
|
|
*/
|
|
void idLobby::ServerUpdateBandwidthTest()
|
|
{
|
|
if( bandwidthChallengeStartTime <= 0 )
|
|
{
|
|
// Not doing a test
|
|
return;
|
|
}
|
|
|
|
if( !verify( IsHost() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
|
|
if( bandwidthChallengeFinished )
|
|
{
|
|
// test is over
|
|
return;
|
|
}
|
|
|
|
idRandom random;
|
|
random.SetSeed( time );
|
|
|
|
bool sentAll = true;
|
|
bool recAll = true;
|
|
|
|
for( int i = 0; i < peers.Num(); ++i )
|
|
{
|
|
peer_t& peer = peers[ i ];
|
|
if( !peer.IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( peer.bandwidthChallengeResults )
|
|
{
|
|
continue;
|
|
}
|
|
recAll = false;
|
|
|
|
if( peer.bandwidthChallengeSendComplete )
|
|
{
|
|
continue;
|
|
}
|
|
sentAll = false;
|
|
|
|
if( time - peer.bandwidthTestLastSendTime < session->GetTitleStorageInt( "net_bw_test_interval", net_bw_test_interval.GetInteger() ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( peer.packetProc->HasMoreFragments() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( peer.bandwidthChallengeStartSendTime == 0 )
|
|
{
|
|
peer.bandwidthChallengeStartSendTime = time;
|
|
}
|
|
|
|
peer.bandwidthTestLastSendTime = time;
|
|
|
|
// Ok, send him a big packet
|
|
byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; // <---- NOTE - When calling ProcessOutgoingMsg with true for oob, we can't go over this size
|
|
idBitMsg msg( buffer, sizeof( buffer ) );
|
|
|
|
msg.WriteLong( peer.bandwidthSequenceNum++ );
|
|
|
|
unsigned int randomSize = Min( ( unsigned int )( sizeof( buffer ) - 12 ), ( unsigned int )session->GetTitleStorageInt( "net_bw_test_packetSizeBytes", net_bw_test_packetSizeBytes.GetInteger() ) );
|
|
msg.WriteLong( randomSize );
|
|
|
|
for( unsigned int j = 0; j < randomSize; j++ )
|
|
{
|
|
msg.WriteByte( random.RandomInt( 255 ) );
|
|
}
|
|
|
|
unsigned int checksum = MD5_BlockChecksum( &buffer[8], randomSize );
|
|
msg.WriteLong( checksum );
|
|
|
|
NET_VERBOSE_PRINT( "Net: Sending bw challenge to peer %d time %d packet size %d\n", i, time, msg.GetSize() );
|
|
|
|
ProcessOutgoingMsg( i, buffer, msg.GetSize(), true, OOB_BANDWIDTH_TEST );
|
|
|
|
if( session->GetTitleStorageInt( "net_bw_test_numPackets", net_bw_test_numPackets.GetInteger() ) > 0 && peer.bandwidthSequenceNum >= net_bw_test_numPackets.GetInteger() )
|
|
{
|
|
int sentBytes = peers[i].packetProc->GetOutgoingBytes() - peers[i].bandwidthTestBytes; // FIXME: this won't include the last sent msg
|
|
peers[i].bandwidthTestBytes = sentBytes; // this now means total bytes sent (we don't care about starting/ending total bytes sent to peer)
|
|
peers[i].bandwidthChallengeSendComplete = true;
|
|
|
|
NET_VERBOSE_PRINT( "Sent enough packets to peer %d for bandwidth test in %dms. Total bytes: %d\n", i, time - bandwidthChallengeStartTime, sentBytes );
|
|
}
|
|
}
|
|
|
|
if( sentAll )
|
|
{
|
|
if( bandwidthChallengeEndTime == 0 )
|
|
{
|
|
// We finished sending all our packets, set the timeout time
|
|
bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_host_timeout", net_bw_test_host_timeout.GetInteger() );
|
|
NET_VERBOSE_PRINT( "Net: finished sending BWC to peers. Waiting until %d to hear back\n", bandwidthChallengeEndTime );
|
|
}
|
|
}
|
|
|
|
if( recAll )
|
|
{
|
|
bandwidthChallengeFinished = true;
|
|
bandwidthChallengeStartTime = 0;
|
|
|
|
}
|
|
else if( bandwidthChallengeEndTime != 0 && bandwidthChallengeEndTime < time )
|
|
{
|
|
// Timed out waiting for someone - throttle them and move on
|
|
NET_VERBOSE_PRINT( "^2Net: timed out waiting for bandwidth challenge results \n" );
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
NET_VERBOSE_PRINT( " Peer[%d] %s. SentAll: %d RecAll: %d\n", i, GetPeerName( i ), peers[i].bandwidthChallengeSendComplete, peers[i].bandwidthChallengeResults );
|
|
if( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults )
|
|
{
|
|
ThrottlePeerSnapRate( i );
|
|
}
|
|
}
|
|
bandwidthChallengeFinished = true;
|
|
bandwidthChallengeStartTime = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::UpdateBandwidthTest
|
|
This will be called on clients to check current state of bandwidth testing
|
|
========================
|
|
*/
|
|
void idLobby::ClientUpdateBandwidthTest()
|
|
{
|
|
if( !verify( !IsHost() ) || !verify( host >= 0 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !peers[host].IsConnected() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( bandwidthChallengeStartTime <= 0 )
|
|
{
|
|
// Not doing a test
|
|
return;
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
if( bandwidthChallengeEndTime > time )
|
|
{
|
|
// Test is still going on
|
|
return;
|
|
}
|
|
|
|
// Its been long enough since we last received bw test msg. So lets send the results to the server
|
|
byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
|
|
idBitMsg msg( buffer, sizeof( buffer ) );
|
|
|
|
// Send total time it took to receive all the msgs
|
|
// (note, subtract net_bw_test_timeout to get 'last recevied bandwidth test packet')
|
|
// (^^ Note if the last packet is fragmented and we never get it, this is technically wrong!)
|
|
int totalTime = ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ) - bandwidthChallengeStartTime;
|
|
msg.WriteLong( totalTime );
|
|
|
|
// Send total number of complete, in order packets we got
|
|
msg.WriteLong( bandwidthChallengeNumGoodSeq );
|
|
|
|
// Send the overall average bandwidth in KBS
|
|
// Note that sending the number of good packets is not enough. If the packets going out are fragmented, and we
|
|
// drop fragments, the number of good sequences will be lower than the bandwidth we actually received.
|
|
int totalIncomingBytes = peers[host].packetProc->GetIncomingBytes() - peers[host].bandwidthTestBytes;
|
|
msg.WriteLong( totalIncomingBytes );
|
|
|
|
idLib::Printf( "^3Finished Bandwidth test: \n" );
|
|
idLib::Printf( " Total time: %d\n", totalTime );
|
|
idLib::Printf( " Num good packets: %d\n", bandwidthChallengeNumGoodSeq );
|
|
idLib::Printf( " Total received byes: %d\n\n", totalIncomingBytes );
|
|
|
|
bandwidthChallengeStartTime = 0;
|
|
bandwidthChallengeNumGoodSeq = 0;
|
|
|
|
QueueReliableMessage( host, RELIABLE_BANDWIDTH_VALUES, msg.GetReadData(), msg.GetSize() );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleBandwidhTestValue
|
|
========================
|
|
*/
|
|
void idLobby::HandleBandwidhTestValue( int p, idBitMsg& msg )
|
|
{
|
|
if( !IsHost() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
idLib::Printf( "Received RELIABLE_BANDWIDTH_CHECK %d\n", Sys_Milliseconds() );
|
|
|
|
if( bandwidthChallengeStartTime < 0 || bandwidthChallengeFinished )
|
|
{
|
|
idLib::Warning( "Received bandwidth test results too early from peer %d", p );
|
|
return;
|
|
}
|
|
|
|
int totalTime = msg.ReadLong();
|
|
int totalGoodSeq = msg.ReadLong();
|
|
int totalReceivedBytes = msg.ReadLong();
|
|
|
|
// This is the % of complete packets we received. If the packets used in the BWC are big enough to fragment, then pctPackets
|
|
// will be lower than bytesPct (we will have received a larger PCT of overall bandwidth than PCT of full packets received).
|
|
// Im not sure if this is a useful distinction or not, but it may be good to compare against for now.
|
|
float pctPackets = peers[p].bandwidthSequenceNum > 0 ? ( float ) totalGoodSeq / ( float )peers[p].bandwidthSequenceNum : -1.0f;
|
|
|
|
// This is the % of total bytes sent/bytes received.
|
|
float bytesPct = peers[p].bandwidthTestBytes > 0 ? ( float ) totalReceivedBytes / ( float )peers[p].bandwidthTestBytes : -1.0f;
|
|
|
|
// Calculate overall bandwidth for the test. That is, total amount received over time.
|
|
// We may want to expand this to also factor in an average instantaneous rate.
|
|
// For now we are mostly concerned with culling out poor performing clients
|
|
float peerKBS = -1.0f;
|
|
if( verify( totalTime > 0 ) )
|
|
{
|
|
peerKBS = ( ( float )totalReceivedBytes / 1024.0f ) / MS2SEC( totalTime );
|
|
}
|
|
|
|
int totalSendTime = peers[p].bandwidthTestLastSendTime - peers[p].bandwidthChallengeStartSendTime;
|
|
float outgoingKBS = -1.0f;
|
|
if( verify( totalSendTime > 0 ) )
|
|
{
|
|
outgoingKBS = ( ( float )peers[p].bandwidthTestBytes / 1024.0f ) / MS2SEC( totalSendTime );
|
|
}
|
|
|
|
float pctKBS = peerKBS / outgoingKBS;
|
|
|
|
bool failedRate = ( pctKBS < session->GetTitleStorageFloat( "net_bw_test_throttle_rate_pct", net_bw_test_throttle_rate_pct.GetFloat() ) );
|
|
bool failedByte = ( bytesPct < session->GetTitleStorageFloat( "net_bw_test_throttle_byte_pct", net_bw_test_throttle_byte_pct.GetFloat() ) );
|
|
bool failedSeq = ( pctPackets < session->GetTitleStorageFloat( "net_bw_test_throttle_seq_pct", net_bw_test_throttle_seq_pct.GetFloat() ) );
|
|
|
|
idLib::Printf( "^3Finished Bandwidth test %s: \n", GetPeerName( p ) );
|
|
idLib::Printf( " Total time: %dms\n", totalTime );
|
|
idLib::Printf( " %sNum good packets: %d (%.2f%)\n", ( failedSeq ? "^1" : "^2" ), totalGoodSeq, pctPackets );
|
|
idLib::Printf( " %sTotal received bytes: %d (%.2f%)\n", ( failedByte ? "^1" : "^2" ), totalReceivedBytes, bytesPct );
|
|
idLib::Printf( " %sEffective downstream: %.2fkbs (host: %.2fkbs) -> %.2f%\n\n", ( failedRate ? "^1" : "^2" ), peerKBS, outgoingKBS, pctKBS );
|
|
|
|
// If shittConnection(totalTime, totalGoodSeq/totalSeq, totalReceivedBytes/totalSentBytes)
|
|
// throttle this user:
|
|
// peers[p].throttledSnapRate = baseSnapRate * 2
|
|
if( failedRate || failedByte || failedSeq )
|
|
{
|
|
ThrottlePeerSnapRate( p );
|
|
}
|
|
|
|
|
|
// See if we are finished
|
|
peers[p].bandwidthChallengeResults = true;
|
|
bandwidthChallengeFinished = true;
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
if( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults )
|
|
{
|
|
bandwidthChallengeFinished = false;
|
|
}
|
|
}
|
|
|
|
if( bandwidthChallengeFinished )
|
|
{
|
|
bandwidthChallengeStartTime = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendPingValues
|
|
Host only
|
|
Periodically send all peers' pings to all peers (for the UI).
|
|
========================
|
|
*/
|
|
void idLobby::SendPingValues()
|
|
{
|
|
if( !verify( IsHost() ) )
|
|
{
|
|
// paranoia
|
|
return;
|
|
}
|
|
|
|
const int now = Sys_Milliseconds();
|
|
|
|
if( nextSendPingValuesTime > now )
|
|
{
|
|
return;
|
|
}
|
|
|
|
nextSendPingValuesTime = now + PING_INTERVAL_MS;
|
|
|
|
pktPingValues_t packet;
|
|
|
|
memset( &packet, 0, sizeof( packet ) );
|
|
|
|
for( int i = 0; i < peers.Max(); ++i )
|
|
{
|
|
if( i >= peers.Num() )
|
|
{
|
|
packet.pings[ i ] = -1;
|
|
}
|
|
else if( peers[ i ].IsConnected() )
|
|
{
|
|
packet.pings[ i ] = peers[ i ].lastPingRtt;
|
|
}
|
|
else
|
|
{
|
|
packet.pings[ i ] = -1;
|
|
}
|
|
}
|
|
|
|
byte packetCopy[ sizeof( packet ) ];
|
|
idBitMsg msg( packetCopy, sizeof( packetCopy ) );
|
|
for( int i = 0; i < peers.Max(); ++i )
|
|
{
|
|
msg.WriteShort( packet.pings[ i ] );
|
|
}
|
|
|
|
for( int i = 0; i < peers.Num(); i++ )
|
|
{
|
|
if( peers[ i ].IsConnected() )
|
|
{
|
|
QueueReliableMessage( i, RELIABLE_PING_VALUES, msg.GetReadData(), msg.GetSize() );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::PumpPings
|
|
Host: Periodically determine the round-trip time for a packet to all peers, and tell everyone
|
|
what everyone else's ping to the host is so they can display it in the UI.
|
|
Client: Indicate to the player when the server hasn't updated the ping values in too long.
|
|
This is usually going to preceed a connection timeout.
|
|
========================
|
|
*/
|
|
void idLobby::PumpPings()
|
|
{
|
|
if( IsHost() )
|
|
{
|
|
// Calculate ping to all peers
|
|
PingPeers();
|
|
// Send the hosts calculated ping values to each peer to everyone has updated ping times
|
|
SendPingValues();
|
|
// Do bandwidth testing
|
|
ServerUpdateBandwidthTest();
|
|
// Send Migration Data
|
|
SendMigrationGameData();
|
|
}
|
|
else if( IsPeer() )
|
|
{
|
|
ClientUpdateBandwidthTest();
|
|
|
|
if( lastPingValuesRecvTime + PING_INTERVAL_MS + 1000 < Sys_Milliseconds() && migrationInfo.state == MIGRATE_NONE )
|
|
{
|
|
for( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex )
|
|
{
|
|
lobbyUser_t* user = GetLobbyUser( userIndex );
|
|
if( !verify( user != NULL ) )
|
|
{
|
|
continue;
|
|
}
|
|
user->pingMs = 999999;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandleReliablePing
|
|
========================
|
|
*/
|
|
void idLobby::HandleReliablePing( int p, idBitMsg& msg )
|
|
{
|
|
int c, b;
|
|
msg.SaveReadState( c, b );
|
|
|
|
pktPing_t ping;
|
|
|
|
memset( &ping, 0, sizeof( ping ) );
|
|
if( !verify( sizeof( ping ) <= msg.GetRemainingData() ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: Ignoring ping from peer %i because packet was the wrong size\n", p );
|
|
return;
|
|
}
|
|
|
|
ping.timestamp = msg.ReadLong();
|
|
|
|
if( IsHost() )
|
|
{
|
|
// we should probably verify here whether or not this ping was solicited or not
|
|
HandlePingReply( p, ping );
|
|
}
|
|
else
|
|
{
|
|
// this means the server is requesting a ping, so reply
|
|
msg.RestoreReadState( c, b );
|
|
QueueReliableMessage( p, RELIABLE_PING, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandlePingReply
|
|
========================
|
|
*/
|
|
void idLobby::HandlePingReply( int p, const pktPing_t& ping )
|
|
{
|
|
const int now = Sys_Milliseconds();
|
|
|
|
const int rtt = now - ping.timestamp;
|
|
peers[p].lastPingRtt = rtt;
|
|
|
|
for( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex )
|
|
{
|
|
lobbyUser_t* u = GetLobbyUser( userIndex );
|
|
if( u->peerIndex == p )
|
|
{
|
|
u->pingMs = rtt;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::HandlePingValues
|
|
========================
|
|
*/
|
|
void idLobby::HandlePingValues( idBitMsg& msg )
|
|
{
|
|
pktPingValues_t packet;
|
|
memset( &packet, 0, sizeof( packet ) );
|
|
for( int i = 0; i < peers.Max(); ++i )
|
|
{
|
|
packet.pings[ i ] = msg.ReadShort();
|
|
}
|
|
|
|
assert( IsPeer() );
|
|
|
|
lastPingValuesRecvTime = Sys_Milliseconds();
|
|
|
|
for( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex )
|
|
{
|
|
lobbyUser_t* u = GetLobbyUser( userIndex );
|
|
if( u->peerIndex != -1 && verify( u->peerIndex >= 0 && u->peerIndex < MAX_PEERS ) )
|
|
{
|
|
u->pingMs = packet.pings[ u->peerIndex ];
|
|
}
|
|
else
|
|
{
|
|
u->pingMs = 0;
|
|
}
|
|
}
|
|
|
|
// Stuff our ping in the hosts slot
|
|
if( peerIndexOnHost != -1 && verify( peerIndexOnHost >= 0 && peerIndexOnHost < MAX_PEERS ) )
|
|
{
|
|
peers[host].lastPingRtt = packet.pings[ peerIndexOnHost ];
|
|
}
|
|
else
|
|
{
|
|
peers[host].lastPingRtt = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendAnotherFragment
|
|
Other than connectionless sends, this should be the chokepoint for sending packets to peers.
|
|
========================
|
|
*/
|
|
bool idLobby::SendAnotherFragment( int p )
|
|
{
|
|
peer_t& peer = peers[p];
|
|
|
|
if( !peer.IsConnected() ) // Not connected to any mode (party or game), so no need to send
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !peer.packetProc->HasMoreFragments() )
|
|
{
|
|
return false; // No fragments to send for this peer
|
|
}
|
|
|
|
if( !CanSendMoreData( p ) )
|
|
{
|
|
return false; // We need to throttle the sends so we don't saturate the connection
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
|
|
if( time - peer.lastFragmentSendTime < 2 )
|
|
{
|
|
NET_VERBOSE_PRINT( "Too soon to send another packet. Delta: %d \n", ( time - peer.lastFragmentSendTime ) );
|
|
return false; // Too soon to send another fragment
|
|
}
|
|
|
|
peer.lastFragmentSendTime = time;
|
|
|
|
bool sentFragment = false;
|
|
|
|
while( true )
|
|
{
|
|
idBitMsg msg;
|
|
// We use the final packet size here because it has been processed, and no more headers will be added
|
|
byte buffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
|
|
msg.InitWrite( buffer, sizeof( buffer ) );
|
|
|
|
if( !peers[p].packetProc->GetSendFragment( time, peers[p].sessionID, msg ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
|
|
|
|
msg.BeginReading();
|
|
sessionCB->SendRawPacket( peers[p].address, msg.GetReadData(), msg.GetSize(), useDirectPort );
|
|
sentFragment = true;
|
|
break; // Comment this out to send all fragments in one burst
|
|
}
|
|
|
|
if( peer.packetProc->HasMoreFragments() )
|
|
{
|
|
NET_VERBOSE_PRINT( "More packets left after ::SendAnotherFragment\n" );
|
|
}
|
|
|
|
return sentFragment;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::CanSendMoreData
|
|
========================
|
|
*/
|
|
bool idLobby::CanSendMoreData( int p )
|
|
{
|
|
if( !verify( p >= 0 && p < peers.Num() ) )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not a peer\n", p );
|
|
return false;
|
|
}
|
|
peer_t& peer = peers[p];
|
|
if( !peer.IsConnected() )
|
|
{
|
|
NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not connected\n", p );
|
|
return false;
|
|
}
|
|
|
|
return peer.packetProc->CanSendMoreData();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ProcessOutgoingMsg
|
|
========================
|
|
*/
|
|
void idLobby::ProcessOutgoingMsg( int p, const void* data, int size, bool isOOB, int userData )
|
|
{
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
if( peer.GetConnectionState() != CONNECTION_ESTABLISHED )
|
|
{
|
|
idLib::Printf( "peer.GetConnectionState() != CONNECTION_ESTABLISHED\n" );
|
|
return; // Peer not fully connected for this session type, return
|
|
}
|
|
|
|
if( peer.packetProc->HasMoreFragments() )
|
|
{
|
|
idLib::Error( "FATAL: Attempt to process a packet while fragments still need to be sent.\n" ); // We can't handle this case
|
|
}
|
|
|
|
int currentTime = Sys_Milliseconds();
|
|
|
|
// if ( currentTime - peer.lastProcTime < 30 ) {
|
|
// idLib::Printf("ProcessOutgoingMsg called within %dms %s\n", (currentTime - peer.lastProcTime), GetLobbyName() );
|
|
// }
|
|
|
|
peer.lastProcTime = currentTime;
|
|
|
|
if( !isOOB )
|
|
{
|
|
// Keep track of the last time an in-band packet was sent
|
|
// (used for things like knowing when reliables could have been last sent)
|
|
peer.lastInBandProcTime = peer.lastProcTime;
|
|
}
|
|
|
|
idBitMsg msg;
|
|
msg.InitRead( ( byte* )data, size );
|
|
peer.packetProc->ProcessOutgoing( currentTime, msg, isOOB, userData );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::ResendReliables
|
|
========================
|
|
*/
|
|
void idLobby::ResendReliables( int p )
|
|
{
|
|
|
|
peer_t& peer = peers[p];
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( peer.packetProc->HasMoreFragments() )
|
|
{
|
|
return; // We can't send more data while fragments are still being sent out
|
|
}
|
|
|
|
if( !CanSendMoreData( p ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int time = Sys_Milliseconds();
|
|
|
|
const int DEFAULT_MIN_RESEND = 20; // Quicker resend while not in game to speed up resource transmission acks
|
|
const int DEFAULT_MIN_RESEND_INGAME = 100;
|
|
|
|
int resendWait = DEFAULT_MIN_RESEND_INGAME;
|
|
|
|
if( sessionCB->GetState() == idSession::INGAME )
|
|
{
|
|
// setup some minimum waits and account for ping
|
|
resendWait = Max( DEFAULT_MIN_RESEND_INGAME, peer.lastPingRtt / 2 );
|
|
if( lobbyType == TYPE_PARTY )
|
|
{
|
|
resendWait = Max( 500, resendWait ); // party session does not need fast frequency at all once in game
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// don't trust the ping when still loading stuff
|
|
// need to resend fast to speed up transmission of network decls
|
|
resendWait = DEFAULT_MIN_RESEND;
|
|
}
|
|
|
|
if( time - peer.lastInBandProcTime < resendWait )
|
|
{
|
|
// no need to resend reliables if they went out on an in-band packet recently
|
|
return;
|
|
}
|
|
|
|
if( peer.packetProc->NumQueuedReliables() > 0 || peer.packetProc->NeedToSendReliableAck() )
|
|
{
|
|
//NET_VERBOSE_PRINT( "NET: ResendReliables %s\n", GetLobbyName() );
|
|
ProcessOutgoingMsg( p, NULL, 0, false, 0 ); // Force an empty unreliable msg so any reliables will get processed as well
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::PumpPackets
|
|
========================
|
|
*/
|
|
void idLobby::PumpPackets()
|
|
{
|
|
int newTime = Sys_Milliseconds();
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].IsConnected() )
|
|
{
|
|
peers[p].packetProc->RefreshRates( newTime );
|
|
}
|
|
}
|
|
|
|
// Resend reliable msg's (do this before we send out the fragments)
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
ResendReliables( p );
|
|
}
|
|
|
|
// If we haven't sent anything to our peers in a long time, make sure to send an empty packet (so our heartbeat gets updated) so we don't get disconnected
|
|
// NOTE - We used to only send these to the host, but the host needs to also send these to clients
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( !peers[p].IsConnected() || peers[p].packetProc->HasMoreFragments() )
|
|
{
|
|
continue;
|
|
}
|
|
if( newTime - peers[p].lastProcTime > 1000 * PEER_HEARTBEAT_IN_SECONDS )
|
|
{
|
|
//NET_VERBOSE_PRINT( "NET: ProcessOutgoing Heartbeat %s\n", GetLobbyName() );
|
|
ProcessOutgoingMsg( p, NULL, 0, false, 0 );
|
|
}
|
|
}
|
|
|
|
// Send any unsent fragments for each peer (do this last)
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
SendAnotherFragment( p );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::UpdateMatchParms
|
|
========================
|
|
*/
|
|
void idLobby::UpdateMatchParms( const idMatchParameters& p )
|
|
{
|
|
if( !IsHost() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
parms = p;
|
|
|
|
// Update lobbyBackend with parms
|
|
if( lobbyBackend != NULL )
|
|
{
|
|
lobbyBackend->UpdateMatchParms( parms );
|
|
}
|
|
|
|
SendMatchParmsToPeers();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetHostUserName
|
|
========================
|
|
*/
|
|
const char* idLobby::GetHostUserName() const
|
|
{
|
|
if( !IsLobbyActive() )
|
|
{
|
|
return INVALID_LOBBY_USER_NAME;
|
|
}
|
|
return GetPeerName( -1 ); // This will just grab the first user with this peerIndex (which should be the host)
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendReliable
|
|
========================
|
|
*/
|
|
void idLobby::SendReliable( int type, idBitMsg& msg, bool callReceiveReliable /*= true*/, peerMask_t sessionUserMask /*= MAX_UNSIGNED_TYPE( peerMask_t ) */ )
|
|
{
|
|
//assert( lobbyType == GetActingGameStateLobbyType() );
|
|
|
|
assert( type < 256 ); // QueueReliable only accepts a byte for message type
|
|
|
|
// the queuing below sends the whole message
|
|
// I don't know if whole message is a good thing or a bad thing, but if the passed message has been read from already, this is most likely not going to do what the caller expects
|
|
assert( msg.GetReadCount() + msg.GetReadBit() == 0 );
|
|
|
|
if( callReceiveReliable )
|
|
{
|
|
// NOTE: this will put the msg's read status to fully read - which is why the assert check is above
|
|
common->NetReceiveReliable( -1, type, msg );
|
|
}
|
|
|
|
uint32 sentPeerMask = 0;
|
|
for( int i = 0; i < GetNumLobbyUsers(); ++i )
|
|
{
|
|
lobbyUser_t* user = GetLobbyUser( i );
|
|
if( user->peerIndex == -1 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We only care about sending these to peers in our party lobby
|
|
if( user->IsDisconnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Don't sent to a user if they are in the exlusion session user mask
|
|
if( sessionUserMask != 0 && ( sessionUserMask & ( BIT( i ) ) ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int peerIndex = user->peerIndex;
|
|
|
|
if( peerIndex >= peers.Num() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
peer_t& peer = peers[peerIndex];
|
|
|
|
if( !peer.IsConnected() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( ( sentPeerMask & ( 1 << user->peerIndex ) ) == 0 )
|
|
{
|
|
QueueReliableMessage( user->peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData(), msg.GetSize() );
|
|
sentPeerMask |= 1 << user->peerIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendReliableToLobbyUser
|
|
can only be used on the server. will take care of calling locally if addressed to player 0
|
|
========================
|
|
*/
|
|
void idLobby::SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg& msg )
|
|
{
|
|
assert( lobbyType == GetActingGameStateLobbyType() );
|
|
assert( type < 256 ); // QueueReliable only accepts a byte for message type
|
|
assert( IsHost() ); // This function should only be called in the server atm
|
|
const int peerIndex = PeerIndexFromLobbyUser( lobbyUserID );
|
|
if( peerIndex >= 0 )
|
|
{
|
|
// will send the remainder of a message that was started reading through, but not handling a partial byte read
|
|
assert( msg.GetReadBit() == 0 );
|
|
QueueReliableMessage( peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
|
|
}
|
|
else
|
|
{
|
|
common->NetReceiveReliable( -1, type, msg );
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::SendReliableToHost
|
|
will make sure to invoke locally if used on the server
|
|
========================
|
|
*/
|
|
void idLobby::SendReliableToHost( int type, idBitMsg& msg )
|
|
{
|
|
assert( lobbyType == GetActingGameStateLobbyType() );
|
|
|
|
if( IsHost() )
|
|
{
|
|
common->NetReceiveReliable( -1, type, msg );
|
|
}
|
|
else
|
|
{
|
|
// will send the remainder of a message that was started reading through, but not handling a partial byte read
|
|
assert( msg.GetReadBit() == 0 );
|
|
QueueReliableMessage( host, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================================================================================================
|
|
idLobby::reliablePlayerToPlayerHeader_t
|
|
================================================================================================
|
|
*/
|
|
|
|
/*
|
|
========================
|
|
idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t
|
|
========================
|
|
*/
|
|
idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t() : fromSessionUserIndex( -1 ), toSessionUserIndex( -1 )
|
|
{
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idSessionLocal::reliablePlayerToPlayerHeader_t::Read
|
|
========================
|
|
*/
|
|
bool idLobby::reliablePlayerToPlayerHeader_t::Read( idLobby* lobby, idBitMsg& msg )
|
|
{
|
|
assert( lobby != NULL );
|
|
|
|
lobbyUserID_t lobbyUserIDFrom;
|
|
lobbyUserID_t lobbyUserIDTo;
|
|
|
|
lobbyUserIDFrom.ReadFromMsg( msg );
|
|
lobbyUserIDTo.ReadFromMsg( msg );
|
|
|
|
fromSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDFrom );
|
|
toSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDTo );
|
|
|
|
if( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::reliablePlayerToPlayerHeader_t::Write
|
|
========================
|
|
*/
|
|
bool idLobby::reliablePlayerToPlayerHeader_t::Write( idLobby* lobby, idBitMsg& msg )
|
|
{
|
|
|
|
|
|
if( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
lobby->GetLobbyUser( fromSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
|
|
lobby->GetLobbyUser( toSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetNumActiveLobbyUsers
|
|
========================
|
|
*/
|
|
int idLobby::GetNumActiveLobbyUsers() const
|
|
{
|
|
int numActive = 0;
|
|
for( int i = 0; i < GetNumLobbyUsers(); ++i )
|
|
{
|
|
if( !GetLobbyUser( i )->IsDisconnected() )
|
|
{
|
|
numActive++;
|
|
}
|
|
}
|
|
return numActive;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::AllPeersInGame
|
|
========================
|
|
*/
|
|
bool idLobby::AllPeersInGame() const
|
|
{
|
|
assert( lobbyType == GetActingGameStateLobbyType() ); // This function doesn't make sense on a party lobby currently
|
|
|
|
for( int p = 0; p < peers.Num(); p++ )
|
|
{
|
|
if( peers[p].IsConnected() && !peers[p].inGame )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::PeerIndexFromLobbyUser
|
|
========================
|
|
*/
|
|
int idLobby::PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const
|
|
{
|
|
const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID );
|
|
|
|
const lobbyUser_t* user = GetLobbyUser( lobbyUserIndex );
|
|
|
|
if( user == NULL )
|
|
{
|
|
// This needs to be OK for bot support ( or else add bots at the session level )
|
|
return -1;
|
|
}
|
|
|
|
return user->peerIndex;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetPeerTimeSinceLastPacket
|
|
========================
|
|
*/
|
|
int idLobby::GetPeerTimeSinceLastPacket( int peerIndex ) const
|
|
{
|
|
if( peerIndex < 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
return Sys_Milliseconds() - peers[peerIndex].lastHeartBeat;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLobby::GetActingGameStateLobbyType
|
|
========================
|
|
*/
|
|
idLobby::lobbyType_t idLobby::GetActingGameStateLobbyType() const
|
|
{
|
|
extern idCVar net_useGameStateLobby;
|
|
return ( net_useGameStateLobby.GetBool() ) ? TYPE_GAME_STATE : TYPE_GAME;
|
|
}
|
|
|
|
//========================================================================================================================
|
|
// idLobby::peer_t
|
|
//========================================================================================================================
|
|
|
|
/*
|
|
========================
|
|
idLobby::peer_t::GetConnectionState
|
|
========================
|
|
*/
|
|
idLobby::connectionState_t idLobby::peer_t::GetConnectionState() const
|
|
{
|
|
return connectionState;
|
|
}
|