sys_lobby.cpp (136561B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 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. 23 24 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. 25 26 =========================================================================== 27 */ 28 #pragma hdrstop 29 #include "../idlib/precompiled.h" 30 #include "sys_lobby.h" 31 32 extern idCVar net_connectTimeoutInSeconds; 33 extern idCVar net_headlessServer; 34 35 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)" ); 36 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." ); 37 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." ); 38 39 // NOTE - The snapshot exchange does the bandwidth challenge 40 idCVar net_bw_challenge_enable( "net_bw_challenge_enable", "0", CVAR_BOOL, "Enable pre game bandwidth challenge for throttling snap rate" ); 41 42 idCVar net_bw_test_interval( "net_bw_test_interval", "33", CVAR_INTEGER, "MS - how often to send packets in bandwidth test" ); 43 idCVar net_bw_test_numPackets( "net_bw_test_numPackets", "30", CVAR_INTEGER, "Number of bandwidth challenge packets to send" ); 44 idCVar net_bw_test_packetSizeBytes( "net_bw_test_packetSizeBytes", "1024", CVAR_INTEGER, "Size of each packet to send out" ); 45 idCVar net_bw_test_timeout( "net_bw_test_timeout", "500", CVAR_INTEGER, "MS after receiving a bw test packet that client will time out" ); 46 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" ); 47 48 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" ); 49 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" ); 50 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 %" ); 51 52 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" ); 53 54 idCVar net_skipGoodbye( "net_skipGoodbye", "0", CVAR_BOOL, "" ); 55 56 extern unsigned long NetGetVersionChecksum(); 57 58 /* 59 ======================== 60 idLobby::idLobby 61 ======================== 62 */ 63 idLobby::idLobby() { 64 lobbyType = TYPE_INVALID; 65 sessionCB = NULL; 66 67 localReadSS = NULL; 68 objMemory = NULL; 69 haveSubmittedSnaps = false; 70 71 state = STATE_IDLE; 72 failedReason = FAILED_UNKNOWN; 73 74 host = -1; 75 peerIndexOnHost = -1; 76 isHost = false; 77 needToDisplayMigrateMsg = false; 78 migrateMsgFlags = 0; 79 80 partyToken = 0; // will be initialized later 81 loaded = false; 82 respondToArbitrate = false; 83 waitForPartyOk = false; 84 startLoadingFromHost = false; 85 86 nextSendPingValuesTime = 0; 87 lastPingValuesRecvTime = 0; 88 89 nextSendMigrationGameTime = 0; 90 nextSendMigrationGamePeer = 0; 91 92 bandwidthChallengeStartTime = 0; 93 bandwidthChallengeEndTime = 0; 94 bandwidthChallengeFinished = false; 95 bandwidthChallengeNumGoodSeq = 0; 96 97 lastSnapBspHistoryUpdateSequence = -1; 98 99 assert( userList.Max() == freeUsers.Max() ); 100 assert( userList.Max() == userPool.Max() ); 101 102 userPool.SetNum( userPool.Max() ); 103 104 assert( freeUsers.Num() == 0 ); 105 assert( freeUsers.Num() == 0 ); 106 107 // Initialize free user list 108 for ( int i = 0; i < userPool.Num(); i++ ) { 109 freeUsers.Append( &userPool[i] ); 110 } 111 112 showHostLeftTheSession = false; 113 connectIsFromInvite = false; 114 } 115 116 /* 117 ======================== 118 idLobby::Initialize 119 ======================== 120 */ 121 void idLobby::Initialize( lobbyType_t sessionType_, idSessionCallbacks * callbacks ) { 122 assert( callbacks != NULL ); 123 124 lobbyType = sessionType_; 125 sessionCB = callbacks; 126 127 if ( lobbyType == GetActingGameStateLobbyType() ) { 128 // only needed in multiplayer mode 129 objMemory = (uint8*)Mem_Alloc( SNAP_OBJ_JOB_MEMORY, TAG_NETWORKING ); 130 lzwData = (lzwCompressionData_t*)Mem_Alloc( sizeof( lzwCompressionData_t ), TAG_NETWORKING ); 131 } 132 } 133 134 //=============================================================================== 135 // ** BEGIN PUBLIC INTERFACE *** 136 //=============================================================================== 137 138 /* 139 ======================== 140 idLobby::StartHosting 141 ======================== 142 */ 143 void idLobby::StartHosting( const idMatchParameters & parms_ ) { 144 parms = parms_; 145 146 // Allow common to modify the parms 147 common->OnStartHosting( parms ); 148 149 Shutdown(); // Make sure we're in a shutdown state before proceeding 150 151 assert( GetNumLobbyUsers() == 0 ); 152 assert( lobbyBackend == NULL ); 153 154 // Get the skill level of all the players that will eventually go into the lobby 155 StartCreating(); 156 } 157 158 /* 159 ======================== 160 idLobby::StartFinding 161 ======================== 162 */ 163 void idLobby::StartFinding( const idMatchParameters & parms_ ) { 164 parms = parms_; 165 166 Shutdown(); // Make sure we're in a shutdown state before proceeding 167 168 assert( GetNumLobbyUsers() == 0 ); 169 assert( lobbyBackend == NULL ); 170 171 // Clear search results 172 searchResults.Clear(); 173 174 lobbyBackend = sessionCB->FindLobbyBackend( parms, sessionCB->GetPartyLobby().GetNumLobbyUsers(), sessionCB->GetPartyLobby().GetAverageSessionLevel(), idLobbyBackend::TYPE_GAME ); 175 176 SetState( STATE_SEARCHING ); 177 } 178 179 /* 180 ======================== 181 idLobby::Pump 182 ======================== 183 */ 184 void idLobby::Pump() { 185 186 // Check the heartbeat of all our peers, make sure we shouldn't disconnect from peers that haven't sent a heartbeat in awhile 187 CheckHeartBeats(); 188 189 UpdateHostMigration(); 190 191 UpdateLocalSessionUsers(); 192 193 switch ( state ) { 194 case STATE_IDLE: State_Idle(); break; 195 case STATE_CREATE_LOBBY_BACKEND: State_Create_Lobby_Backend(); break; 196 case STATE_SEARCHING: State_Searching(); break; 197 case STATE_OBTAINING_ADDRESS: State_Obtaining_Address(); break; 198 case STATE_CONNECT_HELLO_WAIT: State_Connect_Hello_Wait(); break; 199 case STATE_FINALIZE_CONNECT: State_Finalize_Connect(); break; 200 case STATE_FAILED: break; 201 default: 202 idLib::Error( "idLobby::Pump: Unknown state." ); 203 } 204 } 205 206 /* 207 ======================== 208 idLobby::ProcessSnapAckQueue 209 ======================== 210 */ 211 void idLobby::ProcessSnapAckQueue() { 212 SCOPED_PROFILE_EVENT( "ProcessSnapAckQueue" ); 213 214 const int SNAP_ACKS_TO_PROCESS_PER_FRAME = 1; 215 216 int numProcessed = 0; 217 218 while ( snapDeltaAckQueue.Num() > 0 && numProcessed < SNAP_ACKS_TO_PROCESS_PER_FRAME ) { 219 if ( ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ) ) { 220 numProcessed++; 221 } 222 snapDeltaAckQueue.RemoveIndex( 0 ); 223 } 224 } 225 226 /* 227 ======================== 228 idLobby::Shutdown 229 ======================== 230 */ 231 void idLobby::Shutdown( bool retainMigrationInfo, bool skipGoodbye ) { 232 233 // Cancel host migration if we were in the process of it and this is the session type that was migrating 234 if ( !retainMigrationInfo && migrationInfo.state != MIGRATE_NONE ) { 235 idLib::Printf( "Cancelling host migration on %s.\n", GetLobbyName() ); 236 EndMigration(); 237 } 238 239 failedReason = FAILED_UNKNOWN; 240 241 if ( lobbyBackend == NULL ) { 242 NET_VERBOSE_PRINT( "NET: ShutdownLobby (already shutdown) (%s)\n", GetLobbyName() ); 243 244 // If we don't have this lobbyBackend type, we better be properly shutdown for this lobby 245 assert( GetNumLobbyUsers() == 0 ); 246 assert( host == -1 ); 247 assert( peerIndexOnHost == -1 ); 248 assert( !isHost ); 249 assert( lobbyType != GetActingGameStateLobbyType() || !loaded ); 250 assert( lobbyType != GetActingGameStateLobbyType() || !respondToArbitrate ); 251 assert( snapDeltaAckQueue.Num() == 0 ); 252 253 // Make sure we don't have old peers connected to this lobby 254 for ( int p = 0; p < peers.Num(); p++ ) { 255 assert( peers[p].GetConnectionState() == CONNECTION_FREE ); 256 } 257 258 state = STATE_IDLE; 259 260 return; 261 } 262 263 NET_VERBOSE_PRINT( "NET: ShutdownLobby (%s)\n", GetLobbyName() ); 264 265 for ( int p = 0; p < peers.Num(); p++ ) { 266 if ( peers[p].GetConnectionState() != CONNECTION_FREE ) { 267 SetPeerConnectionState( p, CONNECTION_FREE, skipGoodbye ); // This will send goodbye's 268 } 269 } 270 271 // Remove any users that weren't handled in ResetPeers 272 // (this will happen as a client, because we won't get the reliable msg from the server since we are severing the connection) 273 for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { 274 lobbyUser_t * user = GetLobbyUser( i ); 275 UnregisterUser( user ); 276 } 277 278 FreeAllUsers(); 279 280 host = -1; 281 peerIndexOnHost = -1; 282 isHost = false; 283 needToDisplayMigrateMsg = false; 284 migrationDlg = GDM_INVALID; 285 286 partyToken = 0; // Reset our party token so we recompute 287 loaded = false; 288 respondToArbitrate = false; 289 waitForPartyOk = false; 290 startLoadingFromHost = false; 291 292 snapDeltaAckQueue.Clear(); 293 294 // Shutdown the lobbyBackend 295 if ( !retainMigrationInfo ) { 296 sessionCB->DestroyLobbyBackend( lobbyBackend ); 297 lobbyBackend = NULL; 298 } 299 300 state = STATE_IDLE; 301 } 302 303 /* 304 ======================== 305 idLobby::HandlePacket 306 ======================== 307 */ 308 void idLobby::HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ) { 309 SCOPED_PROFILE_EVENT( "HandlePacket" ); 310 311 // msg will hold a fully constructed msg using the packet processor 312 byte msgBuffer[ idPacketProcessor::MAX_MSG_SIZE ]; 313 314 idBitMsg msg; 315 msg.InitWrite( msgBuffer, sizeof( msgBuffer ) ); 316 317 int peerNum = FindPeer( remoteAddress, sessionID ); 318 int type = idPacketProcessor::RETURN_TYPE_NONE; 319 int userData = 0; 320 321 if ( peerNum >= 0 ) { 322 if ( !peers[peerNum].IsActive() ) { 323 idLib::Printf( "NET: Received in-band packet from peer %s with no active connection.\n", remoteAddress.ToString() ); 324 return; 325 } 326 type = peers[ peerNum ].packetProc->ProcessIncoming( Sys_Milliseconds(), peers[peerNum].sessionID, fragMsg, msg, userData, peerNum ); 327 } else { 328 if ( !idPacketProcessor::ProcessConnectionlessIncoming( fragMsg, msg, userData ) ) { 329 idLib::Printf( "ProcessConnectionlessIncoming FAILED from %s.\n", remoteAddress.ToString() ); 330 // Not a valid connectionless packet 331 return; 332 } 333 334 // Valid connectionless packets are always RETURN_TYPE_OOB 335 type = idPacketProcessor::RETURN_TYPE_OOB; 336 337 // Find the peer this connectionless msg should go to 338 peerNum = FindPeer( remoteAddress, sessionID, true ); 339 } 340 341 if ( type == idPacketProcessor::RETURN_TYPE_NONE ) { 342 // This packet is not necessarily invalid, it could be a start or middle of a fragmented packet that's not fully constructed. 343 return; 344 } 345 346 if ( peerNum >= 0 ) { 347 // Update their heart beat (only if we've received a valid packet (we've checked type == idPacketProcessor::RETURN_TYPE_NONE)) 348 peers[peerNum].lastHeartBeat = Sys_Milliseconds(); 349 } 350 351 // Handle server query requests. We do this before the STATE_IDLE check. This is so we respond. 352 // We may want to change this to just ignore the request if we are idle, and change the timeout time 353 // on the requesters part to just timeout faster. 354 if ( type == idPacketProcessor::RETURN_TYPE_OOB ) { 355 if ( userData == OOB_MATCH_QUERY || userData == OOB_SYSTEMLINK_QUERY ) { 356 sessionCB->HandleServerQueryRequest( remoteAddress, msg, userData ); 357 return; 358 } 359 if ( userData == OOB_MATCH_QUERY_ACK ) { 360 sessionCB->HandleServerQueryAck( remoteAddress, msg ); 361 return; 362 } 363 } 364 365 if ( type == idPacketProcessor::RETURN_TYPE_OOB ) { 366 if ( userData == OOB_VOICE_AUDIO ) { 367 sessionCB->HandleOobVoiceAudio( remoteAddress, msg ); 368 } else if ( userData == OOB_HELLO ) { 369 // Handle new peer connect request 370 peerNum = HandleInitialPeerConnection( msg, remoteAddress, peerNum ); 371 return; 372 } else if ( userData == OOB_MIGRATE_INVITE ) { 373 NET_VERBOSE_PRINT( "NET: Migration invite for session %s from %s (state = %s)\n", GetLobbyName(), remoteAddress.ToString(), session->GetStateString() ); 374 375 // Get connection info 376 lobbyConnectInfo_t connectInfo; 377 connectInfo.ReadFromMsg( msg ); 378 379 if ( lobbyBackend != NULL && lobbyBackend->GetState() != idLobbyBackend::STATE_FAILED && lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) { // Ignore duplicate invites 380 idLib::Printf( "NET: Already migrated to %s.\n", remoteAddress.ToString() ); 381 return; 382 } 383 384 if ( migrationInfo.state == MIGRATE_NONE ) { 385 if ( IsPeer() && host >= 0 && host < peers.Num() && Sys_Milliseconds() - peers[host].lastHeartBeat > 8 * 1000 ) { 386 // Force migration early if we get an invite, and it has been some time since we've heard from the host 387 PickNewHost(); 388 } else { 389 idLib::Printf( "NET: Ignoring migration invite because we are not migrating %s\n", remoteAddress.ToString() ); 390 SendGoodbye( remoteAddress ); // So they can remove us from their invite list 391 return; 392 } 393 } 394 395 if ( !sessionCB->PreMigrateInvite( *this ) ) { 396 NET_VERBOSE_PRINT( "NET: sessionCB->PreMigrateInvite( *this ) failed from %s\n", remoteAddress.ToString() ); 397 return; 398 } 399 400 // If we are also becoming a new host, see who wins 401 if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) { 402 int inviteIndex = FindMigrationInviteIndex( remoteAddress ); 403 404 if ( inviteIndex != -1 ) { 405 // We found them in our list, check to make sure our ping is better 406 int ping1 = migrationInfo.ourPingMs; 407 lobbyUserID_t userId1 = migrationInfo.ourUserId; 408 int ping2 = migrationInfo.invites[inviteIndex].pingMs; 409 lobbyUserID_t userId2 = migrationInfo.invites[inviteIndex].userId; 410 411 if ( IsBetterHost( ping1, userId1, ping2, userId2 ) ) { 412 idLib::Printf( "NET: Ignoring migration invite from %s, since our ping is better (%i / %i).\n", remoteAddress.ToString(), ping1, ping2 ); 413 return; 414 } 415 } 416 } 417 418 bool fromGame = msg.ReadBool(); 419 420 // Kill the current lobbyBackend 421 Shutdown(); 422 423 // Connect to the lobby 424 ConnectTo( connectInfo, true ); // Pass in true for the invite flag, so we can connect to invite only lobby if we need to 425 426 if ( verify( sessionCB != NULL ) ) { 427 if ( sessionCB->BecomingPeer( *this ) ) { 428 migrationInfo.persistUntilGameEndsData.wasMigratedJoin = true; 429 migrationInfo.persistUntilGameEndsData.wasMigratedGame = fromGame; 430 } 431 } 432 433 } else if ( userData == OOB_GOODBYE || userData == OOB_GOODBYE_W_PARTY || userData == OOB_GOODBYE_FULL ) { 434 HandleGoodbyeFromPeer( peerNum, remoteAddress, userData ); 435 return; 436 } else if ( userData == OOB_RESOURCE_LIST ) { 437 438 if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) { 439 return; 440 } 441 442 if ( peerNum != host ) { 443 NET_VERBOSE_PRINT( "NET: Resource list from non-host %i, %s\n", peerNum, remoteAddress.ToString() ); 444 return; 445 } 446 447 if ( peerNum >= 0 && !peers[peerNum].IsConnected() ) { 448 NET_VERBOSE_PRINT( "NET: Resource list from host with no game connection: %i, %s\n", peerNum, remoteAddress.ToString() ); 449 return; 450 } 451 } else if ( userData == OOB_BANDWIDTH_TEST ) { 452 int seqNum = msg.ReadLong(); 453 // TODO: We should read the random data and verify the MD5 checksum 454 455 int time = Sys_Milliseconds(); 456 bool inOrder = ( seqNum == 0 || peers[peerNum].bandwidthSequenceNum + 1 == seqNum ); 457 int timeSinceLast = 0; 458 459 if ( bandwidthChallengeStartTime <= 0 ) { 460 // Reset the test 461 NET_VERBOSE_PRINT( "\nNET: Starting bandwidth test @ %d\n", time ); 462 bandwidthChallengeStartTime = time; 463 peers[peerNum].bandwidthSequenceNum = 0; 464 peers[peerNum].bandwidthTestBytes = peers[peerNum].packetProc->GetIncomingBytes(); 465 } else { 466 timeSinceLast = time - (bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ); 467 } 468 469 if ( inOrder ) { 470 bandwidthChallengeNumGoodSeq++; 471 } 472 473 bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ); 474 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() ); 475 peers[peerNum].bandwidthSequenceNum = seqNum; 476 477 } else { 478 NET_VERBOSE_PRINT( "NET: Unknown oob packet %d from %s (%d)\n", userData, remoteAddress.ToString(), peerNum ); 479 } 480 } else if ( type == idPacketProcessor::RETURN_TYPE_INBAND ) { 481 // Process in-band message 482 if ( peerNum < 0 ) { 483 idLib::Printf( "NET: In-band message from unknown peer: %s\n", remoteAddress.ToString() ); 484 return; 485 } 486 487 if ( !verify( peers[ peerNum ].address.Compare( remoteAddress ) ) ) { 488 idLib::Printf( "NET: Peer with wrong address: %i, %s\n", peerNum, remoteAddress.ToString() ); 489 return; 490 } 491 492 // Handle reliable 493 int numReliable = peers[ peerNum ].packetProc->GetNumReliables(); 494 for ( int r = 0; r < numReliable; r++ ) { 495 // Just in case one of the reliable msg's cause this peer to disconnect 496 // (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) 497 peerNum = FindPeer( remoteAddress, sessionID ); 498 499 if ( peerNum == -1 ) { 500 idLib::Printf( "NET: Dropped peer while processing reliable msg's: %i, %s\n", peerNum, remoteAddress.ToString() ); 501 break; 502 } 503 504 const byte * reliableData = peers[ peerNum ].packetProc->GetReliable( r ); 505 int reliableSize = peers[ peerNum ].packetProc->GetReliableSize( r ); 506 idBitMsg reliableMsg( reliableData, reliableSize ); 507 reliableMsg.SetSize( reliableSize ); 508 509 HandleReliableMsg( peerNum, reliableMsg ); 510 } 511 512 if ( peerNum == -1 || !peers[ peerNum ].IsConnected() ) { 513 // If the peer still has no connection after HandleReliableMsg, then something is wrong. 514 // (We could have been in CONNECTION_CONNECTING state for this session type, but the first message 515 // we should receive from the server is the ack, otherwise, something went wrong somewhere) 516 idLib::Printf( "NET: In-band message from host with no active connection: %i, %s\n", peerNum, remoteAddress.ToString() ); 517 return; 518 } 519 520 // Handle unreliable part (if any) 521 if ( msg.GetRemainingData() > 0 && loaded ) { 522 if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) { 523 idLib::Printf( "NET: Snapshot msg for non game session lobby %s\n", remoteAddress.ToString() ); 524 return; 525 } 526 527 if ( peerNum == host ) { 528 idSnapShot localSnap; 529 int sequence = -1; 530 int baseseq = -1; 531 bool fullSnap = false; 532 localReadSS = &localSnap; 533 534 // If we are the peer, we assume we only receive snapshot data on the in-band channel 535 const byte * deltaData = msg.GetReadData() + msg.GetReadCount(); 536 int deltaLength = msg.GetRemainingData(); 537 538 if ( peers[ peerNum ].snapProc->ReceiveSnapshotDelta( deltaData, deltaLength, 0, sequence, baseseq, localSnap, fullSnap ) ) { 539 540 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() ) ); 541 542 if ( sessionCB->GetState() != idSession::INGAME && sequence != -1 ) { 543 int seq = peers[ peerNum ].snapProc->GetLastAppendedSequence(); 544 545 // 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 546 byte ackbuffer[32]; 547 idBitMsg ackmsg( ackbuffer, sizeof( ackbuffer ) ); 548 ackmsg.WriteLong( seq ); 549 550 // Add incoming BPS for QoS 551 float incomingBPS = peers[ peerNum ].receivedBps; 552 if ( peers[ peerNum ].receivedBpsIndex != seq ) { 553 incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), peers[host].packetProc->GetIncomingRateBytes() ); 554 peers[ peerNum ].receivedBpsIndex = seq; 555 peers[ peerNum ].receivedBps = incomingBPS; 556 } 557 558 ackmsg.WriteQuantizedUFloat< idLobby::BANDWIDTH_REPORTING_MAX, idLobby::BANDWIDTH_REPORTING_BITS >( incomingBPS ); 559 QueueReliableMessage( host, RELIABLE_SNAPSHOT_ACK, ackbuffer, sizeof( ackbuffer ) ); 560 } 561 } 562 563 if ( fullSnap ) { 564 sessionCB->ReceivedFullSnap(); 565 common->NetReceiveSnapshot( localSnap ); 566 } 567 568 localReadSS = NULL; 569 570 } else { 571 // If we are the host, we assume we only receive usercmds on the inband channel 572 573 int snapNum = 0; 574 uint16 receivedBps_quantized = 0; 575 576 byte usercmdBuffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE]; 577 578 lzwCompressionData_t lzwData; 579 idLZWCompressor lzwCompressor( &lzwData ); 580 lzwCompressor.Start( const_cast<byte *>( msg.GetReadData() ) + msg.GetReadCount(), msg.GetRemainingData() ); 581 lzwCompressor.ReadAgnostic( snapNum ); 582 lzwCompressor.ReadAgnostic( receivedBps_quantized ); 583 int usercmdSize = lzwCompressor.Read( usercmdBuffer, sizeof( usercmdBuffer ), true ); 584 lzwCompressor.End(); 585 586 float receivedBps = ( receivedBps_quantized / (float)( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) ) * (float)idLobby::BANDWIDTH_REPORTING_MAX; 587 if ( peers[ peerNum ].receivedBpsIndex != snapNum ) { 588 peers[ peerNum ].receivedBps = receivedBps; 589 peers[ peerNum ].receivedBpsIndex = snapNum; 590 } 591 592 if ( snapNum < 50 ) { 593 NET_VERBOSE_PRINT( "NET: peer %d ack'd snapNum %d\n", peerNum, snapNum ); 594 } 595 ApplySnapshotDelta( peerNum, snapNum ); 596 597 idBitMsg usercmdMsg( (const byte *)usercmdBuffer, usercmdSize ); 598 common->NetReceiveUsercmds( peerNum, usercmdMsg ); 599 } 600 } 601 } 602 } 603 604 /* 605 ======================== 606 idLobby::HasActivePeers 607 ======================== 608 */ 609 bool idLobby::HasActivePeers() const { 610 for ( int p = 0; p < peers.Num(); p++ ) { 611 if ( peers[p].GetConnectionState() != CONNECTION_FREE ) { 612 return true; 613 } 614 } 615 616 return false; 617 } 618 619 /* 620 ======================== 621 idLobby::NumFreeSlots 622 ======================== 623 */ 624 int idLobby::NumFreeSlots() const { 625 if ( parms.matchFlags & MATCH_JOIN_IN_PROGRESS ) { 626 return parms.numSlots - GetNumConnectedUsers(); 627 } else { 628 return parms.numSlots - GetNumLobbyUsers(); 629 } 630 } 631 632 //=============================================================================== 633 // ** END PUBLIC INTERFACE *** 634 //=============================================================================== 635 636 //=============================================================================== 637 // ** BEGIN STATE CODE *** 638 //=============================================================================== 639 640 const char * idLobby::stateToString[ NUM_STATES ] = { 641 ASSERT_ENUM_STRING( STATE_IDLE, 0 ), 642 ASSERT_ENUM_STRING( STATE_CREATE_LOBBY_BACKEND, 1 ), 643 ASSERT_ENUM_STRING( STATE_SEARCHING, 2 ), 644 ASSERT_ENUM_STRING( STATE_OBTAINING_ADDRESS, 3 ), 645 ASSERT_ENUM_STRING( STATE_CONNECT_HELLO_WAIT, 4 ), 646 ASSERT_ENUM_STRING( STATE_FINALIZE_CONNECT, 5 ), 647 ASSERT_ENUM_STRING( STATE_FAILED, 6 ), 648 }; 649 650 /* 651 ======================== 652 idLobby::State_Idle 653 ======================== 654 */ 655 void idLobby::State_Idle() { 656 // If lobbyBackend is in a failed state, shutdown, go to a failed state ourself, and return 657 if ( lobbyBackend != NULL && lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED ) { 658 HandleConnectionAttemptFailed(); 659 common->Dialog().ClearDialog( GDM_MIGRATING ); 660 common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); 661 common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); 662 return; 663 } 664 665 if ( migrationInfo.persistUntilGameEndsData.hasGameData && sessionCB->GetState() <= idSession::IDLE ) { 666 // This can happen with 'leaveGame' or 'disconnect' since those paths don't go through endMatch 667 // This seems like an ok catch all place but there may be a better way to handle this 668 ResetAllMigrationState(); 669 common->Dialog().ClearDialog( GDM_MIGRATING ); 670 common->Dialog().ClearDialog( GDM_MIGRATING_WAITING ); 671 common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING ); 672 } 673 } 674 675 /* 676 ======================== 677 idLobby::State_Create_Lobby_Backend 678 ======================== 679 */ 680 void idLobby::State_Create_Lobby_Backend() { 681 if ( !verify( lobbyBackend != NULL ) ) { 682 SetState( STATE_FAILED ); 683 return; 684 } 685 686 assert( lobbyBackend != NULL ); 687 688 if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) { 689 const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 ); 690 691 // If we are taking too long, cancel the connection 692 if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) { 693 if ( Sys_Milliseconds() - migrationInfo.migrationStartTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) { 694 SetState( STATE_FAILED ); 695 return; 696 } 697 } 698 } 699 700 if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) { 701 return; // Busy but valid 702 } 703 704 if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { 705 SetState( STATE_FAILED ); 706 return; 707 } 708 709 // Success 710 InitStateLobbyHost(); 711 712 // Set state to idle to signify to session we are done creating 713 SetState( STATE_IDLE ); 714 } 715 716 /* 717 ======================== 718 idLobby::State_Searching 719 ======================== 720 */ 721 void idLobby::State_Searching() { 722 if ( !verify( lobbyBackend != NULL ) ) { 723 SetState( STATE_FAILED ); 724 return; 725 } 726 727 if ( lobbyBackend->GetState() == idLobbyBackend::STATE_SEARCHING ) { 728 return; // Busy but valid 729 } 730 731 if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { 732 SetState( STATE_FAILED ); // Any other lobbyBackend state is invalid 733 return; 734 } 735 736 // Done searching, get results from lobbyBackend 737 lobbyBackend->GetSearchResults( searchResults ); 738 739 if ( searchResults.Num() == 0 ) { 740 // If we didn't get any results, set state to failed 741 SetState( STATE_FAILED ); 742 return; 743 } 744 745 extern idCVar net_maxSearchResultsToTry; 746 const int maxSearchResultsToTry = session->GetTitleStorageInt( "net_maxSearchResultsToTry", net_maxSearchResultsToTry.GetInteger() ); 747 748 if ( searchResults.Num() > maxSearchResultsToTry ) { 749 searchResults.SetNum( maxSearchResultsToTry ); 750 } 751 752 // Set state to idle to signify we are done searching 753 SetState( STATE_IDLE ); 754 } 755 756 /* 757 ======================== 758 idLobby::State_Obtaining_Address 759 ======================== 760 */ 761 void idLobby::State_Obtaining_Address() { 762 if ( lobbyBackend->GetState() == idLobbyBackend::STATE_OBTAINING_ADDRESS ) { 763 return; // Valid but not ready 764 } 765 766 if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { 767 // There was an error, signify to caller 768 failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED; 769 NET_VERBOSE_PRINT("idLobby::State_Obtaining_Address: the lobby backend failed." ); 770 SetState( STATE_FAILED ); 771 return; 772 } 773 774 // 775 // We have the address of the lobbyBackend, we can now send a hello packet 776 // 777 778 // This will be the host for this lobby type 779 host = AddPeer( hostAddress, GenerateSessionID() ); 780 781 // Record start time of connection attempt to the host 782 helloStartTime = Sys_Milliseconds(); 783 lastConnectRequest = helloStartTime; 784 connectionAttempts = 0; 785 786 // Change state to connecting 787 SetState( STATE_CONNECT_HELLO_WAIT ); 788 789 // Send first connect attempt now (we'll send more periodically if we fail to receive an ack) 790 // (we do this after changing state, since the function expects we're in the right state) 791 SendConnectionRequest(); 792 } 793 794 /* 795 ======================== 796 idLobby::State_Finalize_Connect 797 ======================== 798 */ 799 void idLobby::State_Finalize_Connect() { 800 if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) { 801 // Valid but busy 802 return; 803 } 804 805 if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { 806 // Any other state not valid, failed 807 SetState( STATE_FAILED ); 808 return; 809 } 810 811 // Success 812 SetState( STATE_IDLE ); 813 814 // Tell session mgr if this was a migration 815 if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) { 816 sessionCB->BecamePeer( *this ); 817 } 818 } 819 820 /* 821 ======================== 822 idLobby::State_Connect_Hello_Wait 823 ======================== 824 */ 825 void idLobby::State_Connect_Hello_Wait() { 826 if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) { 827 // If the lobbyBackend is in an error state, shut everything down 828 NET_VERBOSE_PRINT( "NET: Lobby is no longer ready while waiting for lobbyType %s hello.\n", GetLobbyName() ); 829 HandleConnectionAttemptFailed(); 830 return; 831 } 832 833 int time = Sys_Milliseconds(); 834 835 const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000; 836 837 if ( timeoutMs != 0 && time - helloStartTime > timeoutMs ) { 838 NET_VERBOSE_PRINT( "NET: Timeout waiting for lobbyType %s for party hello.\n", GetLobbyName() ); 839 HandleConnectionAttemptFailed(); 840 return; 841 } 842 843 if ( connectionAttempts < MAX_CONNECT_ATTEMPTS ) { 844 assert( connectionAttempts >= 1 ); // Should have at least the initial connection attempt 845 846 // See if we need to send another hello request 847 // (keep getting more frequent to increase chance due to possible packet loss, but clamp to MIN_CONNECT_FREQUENCY seconds) 848 // TODO: We could eventually make timing out a function of actual number of attempts rather than just plain time. 849 int resendTime = Max( MIN_CONNECT_FREQUENCY_IN_SECONDS, CONNECT_REQUEST_FREQUENCY_IN_SECONDS / connectionAttempts ) * 1000; 850 851 if ( time - lastConnectRequest > resendTime ) { 852 SendConnectionRequest(); 853 lastConnectRequest = time; 854 } 855 } 856 } 857 858 /* 859 ======================== 860 idLobby::SetState 861 ======================== 862 */ 863 void idLobby::SetState( lobbyState_t newState ) { 864 assert( newState < NUM_STATES ); 865 assert( state < NUM_STATES ); 866 867 verify_array_size( stateToString, NUM_STATES ); 868 869 if ( state == newState ) { 870 NET_VERBOSE_PRINT( "NET: idLobby::SetState: State SAME %s for session %s\n", stateToString[ newState ], GetLobbyName() ); 871 return; 872 } 873 874 // Set the current state 875 NET_VERBOSE_PRINT( "NET: idLobby::SetState: State changing from %s to %s for session %s\n", stateToString[ state ], stateToString[ newState ], GetLobbyName() ); 876 877 state = newState; 878 } 879 880 //=============================================================================== 881 // ** END STATE CODE *** 882 //=============================================================================== 883 884 /* 885 ======================== 886 idLobby::StartCreating 887 ======================== 888 */ 889 void idLobby::StartCreating() { 890 assert( lobbyBackend == NULL ); 891 assert( state == STATE_IDLE ); 892 893 float skillLevel = GetAverageLocalUserLevel( true ); 894 895 lobbyBackend = sessionCB->CreateLobbyBackend( parms, skillLevel, (idLobbyBackend::lobbyBackendType_t)lobbyType ); 896 897 SetState( STATE_CREATE_LOBBY_BACKEND ); 898 } 899 900 /* 901 ======================== 902 idLobby::FindPeer 903 ======================== 904 */ 905 int idLobby::FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID ) { 906 907 bool connectionless = ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY || 908 sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME || 909 sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ); 910 911 if ( connectionless && !ignoreSessionID ) { 912 return -1; // This was meant to be connectionless. FindPeer is meant for connected (or connecting) peers 913 } 914 915 for ( int p = 0; p < peers.Num(); p++ ) { 916 if ( peers[p].GetConnectionState() == CONNECTION_FREE ) { 917 continue; 918 } 919 920 if ( peers[p].address.Compare( remoteAddress ) ) { 921 if ( connectionless && ignoreSessionID ) { 922 return p; 923 } 924 925 // Using a rolling check, so that we account for possible packet loss, and out of order issues 926 if ( IsPeer() ) { 927 idPacketProcessor::sessionId_t searchStart = peers[p].sessionID; 928 929 // Since we only roll the code between matches, we should only need to look ahead a couple increments. 930 // Worse case, if the stars line up, the client doesn't see the new sessionId, and times out, and gets booted. 931 // This should be impossible though, since the timings won't be possible considering how long it takes to end the match, 932 // and restart, and then restart again. 933 int numTries = 2; 934 935 while ( numTries-- > 0 && searchStart != sessionID ) { 936 searchStart = IncrementSessionID( searchStart ); 937 if ( searchStart == sessionID ) { 938 idLib::Printf( "NET: Rolling session ID check found new ID: %i\n", searchStart ); 939 if ( peers[p].packetProc != NULL ) { 940 peers[p].packetProc->VerifyEmptyReliableQueue( RELIABLE_GAME_DATA, RELIABLE_DUMMY_MSG ); 941 } 942 peers[p].sessionID = searchStart; 943 break; 944 } 945 } 946 } 947 948 if ( peers[p].sessionID != sessionID ) { 949 continue; 950 } 951 return p; 952 } 953 } 954 return -1; 955 } 956 957 /* 958 ======================== 959 idLobby::FindAnyPeer 960 Find a peer when we don't know the session id, and we don't care since it's a connectionless msg 961 ======================== 962 */ 963 int idLobby::FindAnyPeer( const lobbyAddress_t & remoteAddress ) const { 964 965 for ( int p = 0; p < peers.Num(); p++ ) { 966 if ( peers[p].GetConnectionState() == CONNECTION_FREE ) { 967 continue; 968 } 969 970 if ( peers[p].address.Compare( remoteAddress ) ) { 971 return p; 972 } 973 } 974 return -1; 975 } 976 977 /* 978 ======================== 979 idLobby::FindFreePeer 980 ======================== 981 */ 982 int idLobby::FindFreePeer() const { 983 984 // Return the first non active peer 985 for ( int p = 0; p < peers.Num(); p++ ) { 986 if ( !peers[p].IsActive() ) { 987 return p; 988 } 989 } 990 return -1; 991 } 992 993 /* 994 ======================== 995 idLobby::AddPeer 996 ======================== 997 */ 998 int idLobby::AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ) { 999 // First, make sure we don't already have this peer 1000 int p = FindPeer( remoteAddress, sessionID ); 1001 assert( p == -1 ); // When using session ID's, we SHOULDN'T find this remoteAddress/sessionID combo 1002 1003 if ( p == -1 ) { 1004 // If we didn't find the peer, we need to add a new one 1005 1006 p = FindFreePeer(); 1007 1008 if ( p == -1 ) { 1009 peer_t newPeer; 1010 p = peers.Append( newPeer ); 1011 } 1012 1013 peer_t & peer = peers[p]; 1014 1015 peer.ResetAllData(); 1016 1017 assert( peer.connectionState == CONNECTION_FREE ); 1018 1019 peer.address = remoteAddress; 1020 1021 peer.sessionID = sessionID; 1022 1023 NET_VERBOSE_PRINT( "NET: Added peer %s at index %i\n", remoteAddress.ToString(), p ); 1024 } else { 1025 NET_VERBOSE_PRINT( "NET: Found peer %s at index %i\n", remoteAddress.ToString(), p ); 1026 } 1027 1028 SetPeerConnectionState( p, CONNECTION_CONNECTING ); 1029 1030 if ( lobbyType == GetActingGameStateLobbyType() ) { 1031 // Reset various flags used in game mode 1032 peers[p].ResetMatchData(); 1033 } 1034 1035 return p; 1036 } 1037 1038 /* 1039 ======================== 1040 idLobby::DisconnectPeerFromSession 1041 ======================== 1042 */ 1043 void idLobby::DisconnectPeerFromSession( int p ) { 1044 if ( !verify( IsHost() ) ) { 1045 return; 1046 } 1047 1048 peer_t & peer = peers[p]; 1049 1050 if ( peer.GetConnectionState() != CONNECTION_FREE ) { 1051 SetPeerConnectionState( p, CONNECTION_FREE ); 1052 } 1053 } 1054 1055 /* 1056 ======================== 1057 idLobby::DisconnectAllPeers 1058 ======================== 1059 */ 1060 void idLobby::DisconnectAllPeers() { 1061 for ( int p = 0; p < peers.Num(); p++ ) { 1062 DisconnectPeerFromSession( p ); 1063 } 1064 } 1065 1066 /* 1067 ======================== 1068 idLobby::SendGoodbye 1069 ======================== 1070 */ 1071 void idLobby::SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull ) { 1072 1073 if ( net_skipGoodbye.GetBool() ) { 1074 return; 1075 } 1076 1077 NET_VERBOSE_PRINT( "NET: Sending goodbye to %s for %s (wasFull = %i)\n", remoteAddress.ToString(), GetLobbyName(), wasFull ); 1078 1079 static const int NUM_REDUNDANT_GOODBYES = 10; 1080 1081 int msgType = OOB_GOODBYE; 1082 1083 if ( wasFull ) { 1084 msgType = OOB_GOODBYE_FULL; 1085 } else if ( lobbyType == TYPE_GAME && ( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) && !( parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) ) { 1086 msgType = OOB_GOODBYE_W_PARTY; 1087 } 1088 1089 for ( int i = 0; i < NUM_REDUNDANT_GOODBYES; i++ ) { 1090 SendConnectionLess( remoteAddress, msgType ); 1091 } 1092 } 1093 1094 /* 1095 ======================== 1096 idLobby::SetPeerConnectionState 1097 ======================== 1098 */ 1099 void idLobby::SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye ) { 1100 1101 if ( !verify( p >= 0 && p < peers.Num() ) ) { 1102 idLib::Printf( "NET: SetPeerConnectionState invalid peer index %i\n", p ); 1103 return; 1104 } 1105 1106 peer_t & peer = peers[p]; 1107 1108 const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType(); 1109 1110 if ( peer.GetConnectionState() == newState ) { 1111 idLib::Printf( "NET: SetPeerConnectionState: Peer already in state %i\n", newState ); 1112 assert( 0 ); // This case means something is most likely bad, and it's the programmers fault 1113 assert( ( peer.packetProc != NULL ) == peer.IsActive() ); 1114 assert( ( ( peer.snapProc != NULL ) == peer.IsActive() ) == ( actingGameStateLobbyType == lobbyType ) ); 1115 return; 1116 } 1117 1118 if ( newState == CONNECTION_CONNECTING ) { 1119 //mem.PushHeap(); 1120 1121 // We better be coming from a free connection state if we are trying to connect 1122 assert( peer.GetConnectionState() == CONNECTION_FREE ); 1123 1124 assert( peer.packetProc == NULL ); 1125 peer.packetProc = new ( TAG_NETWORKING )idPacketProcessor(); 1126 1127 if ( lobbyType == actingGameStateLobbyType ) { 1128 assert( peer.snapProc == NULL ); 1129 peer.snapProc = new ( TAG_NETWORKING )idSnapshotProcessor(); 1130 } 1131 1132 //mem.PopHeap(); 1133 } else if ( newState == CONNECTION_ESTABLISHED ) { 1134 // If we are marking this peer as connected for the first time, make sure this peer was actually trying to connect. 1135 assert( peer.GetConnectionState() == CONNECTION_CONNECTING ); 1136 } else if ( newState == CONNECTION_FREE ) { 1137 // If we are freeing this connection and we had an established connection before, make sure to send a goodbye 1138 if ( peer.GetConnectionState() == CONNECTION_ESTABLISHED && !skipGoodbye ) { 1139 idLib::Printf("SetPeerConnectionState: Sending goodbye to peer %s from session %s\n", peer.address.ToString(), GetLobbyName() ); 1140 SendGoodbye( peer.address ); 1141 } 1142 } 1143 1144 peer.connectionState = newState; 1145 1146 if ( !peer.IsActive() ) { 1147 if ( peer.packetProc != NULL ) { 1148 delete peer.packetProc; 1149 peer.packetProc = NULL; 1150 } 1151 1152 if ( peer.snapProc != NULL ) { 1153 assert( lobbyType == actingGameStateLobbyType ); 1154 delete peer.snapProc; 1155 peer.snapProc = NULL; 1156 } 1157 } 1158 1159 // Do this in case we disconnected the peer 1160 if ( IsHost() ) { 1161 RemoveUsersWithDisconnectedPeers(); 1162 } 1163 } 1164 1165 /* 1166 ======================== 1167 idLobby::QueueReliableMessage 1168 ======================== 1169 */ 1170 void idLobby::QueueReliableMessage( int p, byte type, const byte * data, int dataLen ) { 1171 if ( !verify( p >= 0 && p < peers.Num() ) ) { 1172 return; 1173 } 1174 1175 peer_t & peer = peers[p]; 1176 1177 if ( !peer.IsConnected() ) { 1178 // Don't send to this peer if we don't have an established connection of this session type 1179 NET_VERBOSE_PRINT( "NET: Not sending reliable type %i to peer %i because connectionState is %i\n", type, p, peer.GetConnectionState() ); 1180 return; 1181 } 1182 1183 if ( peer.packetProc->NumQueuedReliables() > 2 ) { 1184 idLib::PrintfIf( false, "NET: peer.packetProc->NumQueuedReliables() > 2: %i (%i / %s)\n", peer.packetProc->NumQueuedReliables(), p, peer.address.ToString() ); 1185 } 1186 1187 if ( !peer.packetProc->QueueReliableMessage( type, data, dataLen ) ) { 1188 // For now, when this happens, disconnect from all session types 1189 NET_VERBOSE_PRINT( "NET: Dropping peer because we overflowed his reliable message queue\n" ); 1190 if ( IsHost() ) { 1191 // Disconnect peer from this session type 1192 DisconnectPeerFromSession( p ); 1193 } else { 1194 Shutdown(); // Shutdown session if we can't queue the reliable 1195 } 1196 } 1197 } 1198 1199 /* 1200 ======================== 1201 idLobby::GetNumConnectedPeers 1202 ======================== 1203 */ 1204 int idLobby::GetNumConnectedPeers() const { 1205 int numConnected = 0; 1206 for ( int i = 0; i < peers.Num(); i++ ) { 1207 if ( peers[i].IsConnected() ) { 1208 numConnected++; 1209 } 1210 } 1211 1212 return numConnected; 1213 } 1214 1215 /* 1216 ======================== 1217 idLobby::GetNumConnectedPeersInGame 1218 ======================== 1219 */ 1220 int idLobby::GetNumConnectedPeersInGame() const { 1221 int numActive = 0; 1222 for ( int i = 0; i < peers.Num(); i++ ) { 1223 if ( peers[i].IsConnected() && peers[i].inGame ) { 1224 numActive++; 1225 } 1226 } 1227 1228 return numActive; 1229 } 1230 1231 1232 /* 1233 ======================== 1234 idLobby::SendMatchParmsToPeers 1235 ======================== 1236 */ 1237 void idLobby::SendMatchParmsToPeers() { 1238 if ( !IsHost() ) { 1239 return; 1240 } 1241 1242 if ( GetNumConnectedPeers() == 0 ) { 1243 return; 1244 } 1245 1246 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; 1247 idBitMsg msg( buffer, sizeof( buffer ) ); 1248 parms.Write( msg ); 1249 1250 for ( int p = 0; p < peers.Num(); p++ ) { 1251 if ( !peers[p].IsConnected() ) { 1252 continue; 1253 } 1254 QueueReliableMessage( p, RELIABLE_MATCH_PARMS, msg.GetReadData(), msg.GetSize() ); 1255 } 1256 } 1257 1258 /* 1259 ======================== 1260 STATIC idLobby::IsReliablePlayerToPlayerType 1261 ======================== 1262 */ 1263 bool idLobby::IsReliablePlayerToPlayerType( byte type ) { 1264 return ( type >= RELIABLE_PLAYER_TO_PLAYER_BEGIN ) && ( type < RELIABLE_PLAYER_TO_PLAYER_END ); 1265 } 1266 1267 /* 1268 ======================== 1269 idLobby::HandleReliablePlayerToPlayerMsg 1270 ======================== 1271 */ 1272 void idLobby::HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ) { 1273 reliablePlayerToPlayerHeader_t info; 1274 int c, b; 1275 msg.SaveReadState( c, b ); // in case we need to forward or fail 1276 1277 if ( !info.Read( this, msg ) ) { 1278 idLib::Warning( "NET: Ignoring invalid reliable player to player message" ); 1279 msg.RestoreReadState( c, b ); 1280 return; 1281 } 1282 1283 const bool isForLocalPlayer = IsSessionUserIndexLocal( info.toSessionUserIndex ); 1284 1285 if ( isForLocalPlayer ) { 1286 HandleReliablePlayerToPlayerMsg( info, msg, type ); 1287 } else if ( IsHost() ) { 1288 const int targetPeer = PeerIndexForSessionUserIndex( info.toSessionUserIndex ); 1289 msg.RestoreReadState( c, b ); 1290 // forward the rest of the data 1291 const byte * data = msg.GetReadData() + msg.GetReadCount(); 1292 int dataLen = msg.GetSize() - msg.GetReadCount(); 1293 1294 QueueReliableMessage( targetPeer, type, data, dataLen ); 1295 } else { 1296 idLib::Warning( "NET: Can't forward reliable message for remote player: I'm not the host" ); 1297 } 1298 } 1299 1300 /* 1301 ======================== 1302 idLobby::HandleReliablePlayerToPlayerMsg 1303 ======================== 1304 */ 1305 void idLobby::HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ) { 1306 #if 0 1307 // Remember that the reliablePlayerToPlayerHeader_t was already removed from the msg 1308 reliablePlayerToPlayer_t type = (reliablePlayerToPlayer_t)( reliableType - RELIABLE_PLAYER_TO_PLAYER_BEGIN ); 1309 1310 switch( type ) { 1311 case RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT: { 1312 sessionCB->HandleReliableVoiceEvent( *this, info.fromSessionUserIndex, info.toSessionUserIndex, msg ); 1313 break; 1314 } 1315 1316 default: { 1317 idLib::Warning( "NET: Ignored unknown player to player reliable type %i", (int) type ); 1318 } 1319 }; 1320 #endif 1321 } 1322 1323 /* 1324 ======================== 1325 idLobby::SendConnectionLess 1326 ======================== 1327 */ 1328 void idLobby::SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ) { 1329 idBitMsg msg( data, dataLen ); 1330 msg.SetSize( dataLen ); 1331 1332 byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; 1333 idBitMsg processedMsg( buffer, sizeof( buffer ) ); 1334 1335 // Process the send 1336 idPacketProcessor::ProcessConnectionlessOutgoing( msg, processedMsg, lobbyType, type ); 1337 1338 const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE ); 1339 1340 // Send it 1341 sessionCB->SendRawPacket( remoteAddress, processedMsg.GetReadData(), processedMsg.GetSize(), useDirectPort ); 1342 } 1343 1344 /* 1345 ======================== 1346 idLobby::SendConnectionRequest 1347 ======================== 1348 */ 1349 void idLobby::SendConnectionRequest() { 1350 // Some sanity checking 1351 assert( state == STATE_CONNECT_HELLO_WAIT ); 1352 assert( peers[host].GetConnectionState() == CONNECTION_CONNECTING ); 1353 assert( GetNumLobbyUsers() == 0 ); 1354 1355 // Buffer to hold connect msg 1356 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; 1357 idBitMsg msg( buffer, sizeof( buffer ) ); 1358 1359 // Add the current version info to the handshake 1360 const unsigned long localChecksum = NetGetVersionChecksum(); 1361 1362 NET_VERBOSE_PRINT( "NET: version = %i\n", localChecksum ); 1363 1364 msg.WriteLong( localChecksum ); 1365 msg.WriteUShort( peers[host].sessionID ); 1366 msg.WriteBool( connectIsFromInvite ); 1367 1368 // We use InitSessionUsersFromLocalUsers here to copy the current local users over to session users simply to have a list 1369 // to send on the initial connection attempt. We immediately clear our session user list once sent. 1370 InitSessionUsersFromLocalUsers( true ); 1371 1372 if ( GetNumLobbyUsers() > 0 ) { 1373 // Fill up the msg with the users on this machine 1374 msg.WriteByte( GetNumLobbyUsers() ); 1375 1376 for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { 1377 GetLobbyUser( u )->WriteToMsg( msg ); 1378 } 1379 } else { 1380 FreeAllUsers(); 1381 SetState( STATE_FAILED ); 1382 1383 return; 1384 } 1385 1386 // We just used these users to fill up the msg above, we will get the real list from the server if we connect. 1387 FreeAllUsers(); 1388 1389 NET_VERBOSE_PRINT( "NET: Sending hello to: %s (lobbyType: %s, session ID %i, attempt: %i)\n", hostAddress.ToString(), GetLobbyName(), peers[host].sessionID, connectionAttempts ); 1390 1391 SendConnectionLess( hostAddress, OOB_HELLO, msg.GetReadData(), msg.GetSize() ); 1392 1393 connectionAttempts++; 1394 } 1395 1396 /* 1397 ======================== 1398 idLobby::ConnectTo 1399 1400 Fires off a request to get the address of a lobbyBackend owner, and then attempts to connect (eventually handled in HandleObtainingLobbyOwnerAddress) 1401 ======================== 1402 */ 1403 void idLobby::ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ) { 1404 NET_VERBOSE_PRINT( "NET: idSessionLocal::ConnectTo: fromInvite = %i\n", fromInvite ); 1405 1406 // Make sure current session is shutdown 1407 Shutdown(); 1408 1409 connectIsFromInvite = fromInvite; 1410 1411 lobbyBackend = sessionCB->JoinFromConnectInfo( connectInfo, (idLobbyBackend::lobbyBackendType_t)lobbyType ); 1412 1413 // First, we need the address of the lobbyBackend owner 1414 lobbyBackend->GetOwnerAddress( hostAddress ); 1415 1416 SetState( STATE_OBTAINING_ADDRESS ); 1417 1418 } 1419 1420 /* 1421 ======================== 1422 idLobby::HandleGoodbyeFromPeer 1423 ======================== 1424 */ 1425 void idLobby::HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ) { 1426 if ( migrationInfo.state != MIGRATE_NONE ) { 1427 // If this peer is on our invite list, remove them 1428 for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) { 1429 if ( migrationInfo.invites[i].address.Compare( remoteAddress, true ) ) { 1430 migrationInfo.invites.RemoveIndex( i ); 1431 break; 1432 } 1433 } 1434 } 1435 1436 if ( peerNum < 0 ) { 1437 NET_VERBOSE_PRINT( "NET: Goodbye from unknown peer %s on session %s\n", remoteAddress.ToString(), GetLobbyName() ); 1438 return; 1439 } 1440 1441 if ( peers[peerNum].GetConnectionState() == CONNECTION_FREE ) { 1442 NET_VERBOSE_PRINT( "NET: Goodbye from peer %s on session %s that is not connected\n", remoteAddress.ToString(), GetLobbyName() ); 1443 return; 1444 } 1445 1446 if ( IsHost() ) { 1447 // Goodbye from peer, remove him 1448 NET_VERBOSE_PRINT( "NET: Goodbye from peer %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() ); 1449 DisconnectPeerFromSession( peerNum ); 1450 } else { 1451 // Let session handler take care of this 1452 NET_VERBOSE_PRINT( "NET: Goodbye from host %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() ); 1453 sessionCB->GoodbyeFromHost( *this, peerNum, remoteAddress, msgType ); 1454 } 1455 } 1456 1457 /* 1458 ======================== 1459 idLobby::HandleGoodbyeFromPeer 1460 ======================== 1461 */ 1462 void idLobby::HandleConnectionAttemptFailed() { 1463 Shutdown(); 1464 failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED; 1465 SetState( STATE_FAILED ); 1466 1467 if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) { 1468 sessionCB->FailedGameMigration( *this ); 1469 } 1470 1471 ResetAllMigrationState(); 1472 1473 needToDisplayMigrateMsg = false; 1474 migrateMsgFlags = 0; 1475 } 1476 1477 /* 1478 ======================== 1479 idLobby::ConnectToNextSearchResult 1480 ======================== 1481 */ 1482 bool idLobby::ConnectToNextSearchResult() { 1483 if ( lobbyType != TYPE_GAME ) { 1484 return false; // Only game sessions use matchmaking searches 1485 } 1486 1487 // End current session lobby (this WON'T free search results) 1488 Shutdown(); 1489 1490 if ( searchResults.Num() == 0 ) { 1491 return false; // No more search results to connect to, give up 1492 } 1493 1494 // Get next search result 1495 lobbyConnectInfo_t connectInfo = searchResults[0]; 1496 1497 // Remove this search result 1498 searchResults.RemoveIndex( 0 ); 1499 1500 // If we are connecting to a game lobby, tell our party to connect to this lobby as well 1501 if ( lobbyType == TYPE_GAME && sessionCB->GetPartyLobby().IsLobbyActive() ) { 1502 sessionCB->GetPartyLobby().SendMembersToLobby( lobbyType, connectInfo, true ); 1503 } 1504 1505 // Attempt to connect the lobby 1506 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 1507 1508 // Clear the "Lobby was Full" dialog in case it's up, since we are going to try to connect to a different lobby now 1509 common->Dialog().ClearDialog( GDM_LOBBY_FULL ); 1510 1511 return true; // Notify caller we are attempting to connect 1512 } 1513 1514 /* 1515 ======================== 1516 idLobby::CheckVersion 1517 ======================== 1518 */ 1519 bool idLobby::CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ) { 1520 const unsigned long remoteChecksum = msg.ReadLong(); 1521 1522 if ( net_checkVersion.GetInteger() == 1 ) { 1523 const unsigned long localChecksum = NetGetVersionChecksum(); 1524 1525 NET_VERBOSE_PRINT( "NET: Comparing handshake version - localChecksum = %i, remoteChecksum = %i\n", localChecksum, remoteChecksum ); 1526 return ( remoteChecksum == localChecksum ); 1527 } 1528 return true; 1529 } 1530 1531 /* 1532 ======================== 1533 idLobby::VerifyNumConnectingUsers 1534 Make sure number of users connecting is valid, and make sure we have enough room 1535 ======================== 1536 */ 1537 bool idLobby::VerifyNumConnectingUsers( idBitMsg & msg ) { 1538 int c, b; 1539 msg.SaveReadState( c, b ); 1540 const int numUsers = msg.ReadByte(); 1541 msg.RestoreReadState( c, b ); 1542 1543 const int numFreeSlots = NumFreeSlots(); 1544 1545 NET_VERBOSE_PRINT( "NET: VerifyNumConnectingUsers %i users, %i free slots for %s\n", numUsers, numFreeSlots, GetLobbyName() ); 1546 1547 if ( numUsers <= 0 || numUsers > MAX_PLAYERS - 1 ) { 1548 NET_VERBOSE_PRINT( "NET: Invalid numUsers %i\n", numUsers ); 1549 return false; 1550 } else if ( numUsers > numFreeSlots ) { 1551 NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available\n", numUsers, numFreeSlots ); 1552 return false; 1553 } else if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY && sessionCB->GetGameLobby().IsLobbyActive() && !IsMigrating() ) { 1554 const int numFreeGameSlots = sessionCB->GetGameLobby().NumFreeSlots(); 1555 1556 if ( numUsers > numFreeGameSlots ) { 1557 NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available on the active game session\n", numUsers, numFreeGameSlots ); 1558 return false; 1559 } 1560 } 1561 1562 return true; 1563 } 1564 1565 /* 1566 ======================== 1567 idLobby::VerifyLobbyUserIDs 1568 ======================== 1569 */ 1570 bool idLobby::VerifyLobbyUserIDs( idBitMsg & msg ) { 1571 int c, b; 1572 msg.SaveReadState( c, b ); 1573 const int numUsers = msg.ReadByte(); 1574 1575 // Add the new users to our own list 1576 for ( int u = 0; u < numUsers; u++ ) { 1577 lobbyUser_t newUser; 1578 1579 // Read in the new user 1580 newUser.ReadFromMsg( msg ); 1581 1582 if ( GetLobbyUserIndexByID( newUser.lobbyUserID, true ) != -1 ) { 1583 msg.RestoreReadState( c, b ); 1584 return false; 1585 } 1586 } 1587 1588 msg.RestoreReadState( c, b ); 1589 1590 return true; 1591 } 1592 1593 /* 1594 ======================== 1595 idLobby::HandleInitialPeerConnection 1596 Received on an initial peer connect request (OOB_HELLO) 1597 ======================== 1598 */ 1599 int idLobby::HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ) { 1600 if ( net_ignoreConnects.GetInteger() > 0 ) { 1601 if ( net_ignoreConnects.GetInteger() == 2 ) { 1602 SendGoodbye( peerAddress ); 1603 } 1604 return -1; 1605 } 1606 1607 if ( !IsHost() ) { 1608 NET_VERBOSE_PRINT( "NET: Got connectionless hello from peer %s on session, and we are not a host\n", peerAddress.ToString() ); 1609 SendGoodbye( peerAddress ); 1610 return -1; 1611 } 1612 1613 // See if this is a peer migrating to us, if so, remove them from our invite list 1614 bool migrationInvite = false; 1615 int migrationGameData = -1; 1616 1617 1618 for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) { 1619 if ( migrationInfo.invites[i].address.Compare( peerAddress, true ) ) { 1620 1621 migrationGameData = migrationInfo.invites[i].migrationGameData; 1622 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) 1623 migrationInvite = true; 1624 NET_VERBOSE_PRINT( "^2NET: Response from migration invite %s. GameData: %d\n", peerAddress.ToString(), migrationGameData ); 1625 } 1626 } 1627 1628 if ( !MatchTypeIsJoinInProgress( parms.matchFlags ) && lobbyType == TYPE_GAME && migrationInfo.persistUntilGameEndsData.wasMigratedHost && IsMigratedStatsGame() && !migrationInvite ) { 1629 // No matter what, don't let people join migrated game sessions that are going to continue on to the same game 1630 // Not on invite list in a migrated game session - bounce him 1631 NET_VERBOSE_PRINT( "NET: Denying game connection from %s since not on migration invite list\n", peerAddress.ToString() ); 1632 for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) { 1633 NET_VERBOSE_PRINT( " Invite[%d] addr: %s\n", i, migrationInfo.invites[i].address.ToString() ); 1634 } 1635 SendGoodbye( peerAddress ); 1636 return -1; 1637 } 1638 1639 1640 if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) { 1641 // If this is for a game connection, make sure we have a game lobby 1642 if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() < idSession::GAME_LOBBY ) { 1643 NET_VERBOSE_PRINT( "NET: Denying game connection from %s because we don't have a game lobby\n", peerAddress.ToString() ); 1644 SendGoodbye( peerAddress ); 1645 return -1; 1646 } 1647 } else { 1648 // If this is for a game connection, make sure we are in the game lobby 1649 if ( lobbyType == TYPE_GAME && sessionCB->GetState() != idSession::GAME_LOBBY ) { 1650 NET_VERBOSE_PRINT( "NET: Denying game connection from %s while not in game lobby\n", peerAddress.ToString() ); 1651 SendGoodbye( peerAddress ); 1652 return -1; 1653 } 1654 1655 // If this is for a party connection, make sure we are not in game, unless this was for host migration invite 1656 if ( !migrationInvite && lobbyType == TYPE_PARTY && ( sessionCB->GetState() == idSession::INGAME || sessionCB->GetState() == idSession::LOADING ) ) { 1657 NET_VERBOSE_PRINT( "NET: Denying party connection from %s because we were already in a game\n", peerAddress.ToString() ); 1658 SendGoodbye( peerAddress ); 1659 return -1; 1660 } 1661 } 1662 1663 if ( !CheckVersion( msg, peerAddress ) ) { 1664 idLib::Printf( "NET: Denying user %s with wrong version number\n", peerAddress.ToString() ); 1665 SendGoodbye( peerAddress ); 1666 return -1; 1667 } 1668 1669 idPacketProcessor::sessionId_t sessionID = msg.ReadUShort(); 1670 1671 // Check to see if this is a peer trying to connect with a different sessionID 1672 // If the peer got abruptly disconnected, the peer could be trying to reconnect from a non clean disconnect 1673 if ( peerNum >= 0 ) { 1674 peer_t & existingPeer = peers[peerNum]; 1675 1676 assert( existingPeer.GetConnectionState() != CONNECTION_FREE ); 1677 1678 if ( existingPeer.sessionID == sessionID ) { 1679 return peerNum; // If this is the same sessionID, then assume redundant connection attempt 1680 } 1681 1682 // 1683 // This peer must be trying to reconnect from a previous abrupt disconnect 1684 // 1685 1686 NET_VERBOSE_PRINT( "NET: Reconnecting peer %s for session %s\n", peerAddress.ToString(), GetLobbyName() ); 1687 1688 // Assume a peer is trying to reconnect from a non clean disconnect 1689 // We want to set the connection back to FREE manually, so we don't send a goodbye 1690 existingPeer.connectionState = CONNECTION_FREE; 1691 1692 if ( existingPeer.packetProc != NULL ) { 1693 delete existingPeer.packetProc; 1694 existingPeer.packetProc = NULL; 1695 } 1696 1697 if ( existingPeer.snapProc != NULL ) { 1698 assert( lobbyType == TYPE_GAME ); // Only games sessions should be creating snap processors 1699 delete existingPeer.snapProc; 1700 existingPeer.snapProc = NULL; 1701 } 1702 1703 RemoveUsersWithDisconnectedPeers(); 1704 1705 peerNum = -1; 1706 } 1707 1708 // See if this was from an invite we sent out. If it wasn't, make sure we aren't invite only 1709 const bool fromInvite = msg.ReadBool(); 1710 1711 if ( !fromInvite && MatchTypeInviteOnly( parms.matchFlags ) ) { 1712 idLib::Printf( "NET: Denying user %s because they were not invited to an invite only match\n", peerAddress.ToString() ); 1713 SendGoodbye( peerAddress ); 1714 return -1; 1715 } 1716 1717 // Make sure we have room for the users connecting 1718 if ( !VerifyNumConnectingUsers( msg ) ) { 1719 NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to being out of user slots\n", peerAddress.ToString(), GetLobbyName() ); 1720 SendGoodbye( peerAddress, true ); 1721 return -1; 1722 } 1723 1724 // Make sure there are no lobby id conflicts 1725 if ( !verify( VerifyLobbyUserIDs( msg ) ) ) { 1726 NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to lobby id conflict\n", peerAddress.ToString(), GetLobbyName() ); 1727 SendGoodbye( peerAddress, true ); 1728 return -1; 1729 } 1730 1731 // Calling AddPeer will set our connectionState to this peer as CONNECTION_CONNECTING (which will get set to CONNECTION_ESTABLISHED below) 1732 peerNum = AddPeer( peerAddress, sessionID ); 1733 1734 peer_t & newPeer = peers[peerNum]; 1735 1736 assert( newPeer.GetConnectionState() == CONNECTION_CONNECTING ); 1737 assert( lobbyType != GetActingGameStateLobbyType() || newPeer.snapProc != NULL ); 1738 1739 // First, add users from this new peer to our user list 1740 // (which will then forward the list to all peers except peerNum) 1741 AddUsersFromMsg( msg, peerNum ); 1742 1743 // Mark the peer as connected for this session type 1744 SetPeerConnectionState( peerNum, CONNECTION_ESTABLISHED ); 1745 1746 // Update their heart beat to current 1747 newPeer.lastHeartBeat = Sys_Milliseconds(); 1748 1749 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; 1750 idBitMsg outmsg( buffer, sizeof( buffer ) ); 1751 1752 // Let them know their peer index on this host 1753 // peerIndexOnHost (put this here so it shows up in search results when finding out where it's used/referenced) 1754 outmsg.WriteLong( peerNum ); 1755 1756 // If they are connecting to our party lobby, let them know the party token 1757 if ( lobbyType == TYPE_PARTY ) { 1758 outmsg.WriteLong( GetPartyTokenAsHost() ); 1759 } 1760 1761 if ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) { 1762 // If this is a game session, reset the loading and ingame flags 1763 newPeer.loaded = false; 1764 newPeer.inGame = false; 1765 } 1766 1767 // Write out current match parms 1768 parms.Write( outmsg ); 1769 1770 // Send list of existing users to this new peer 1771 // (the users from the new peer will also be in this list, since we already called AddUsersFromMsg) 1772 outmsg.WriteByte( GetNumLobbyUsers() ); 1773 1774 for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { 1775 GetLobbyUser( u )->WriteToMsg( outmsg ); 1776 } 1777 1778 lobbyBackend->FillMsgWithPostConnectInfo( outmsg ); 1779 1780 NET_VERBOSE_PRINT( "NET: Sending response to %s, lobbyType %s, sessionID %i\n", peerAddress.ToString(), GetLobbyName(), sessionID ); 1781 1782 QueueReliableMessage( peerNum, RELIABLE_HELLO, outmsg.GetReadData(), outmsg.GetSize() ); 1783 1784 if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) { 1785 // If have an active game lobby, and someone joins our party, tell them to join our game 1786 if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY ) { 1787 SendPeerMembersToLobby( peerNum, TYPE_GAME, false ); 1788 } 1789 1790 // We are are ingame, then start the client loading immediately 1791 if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() >= idSession::LOADING ) { 1792 idLib::Printf( "******* JOIN IN PROGRESS ********\n" ); 1793 if ( sessionCB->GetState() == idSession::INGAME ) { 1794 newPeer.pauseSnapshots = true; // Since this player joined in progress, let game dictate when to start sending snaps 1795 } 1796 QueueReliableMessage( peerNum, idLobby::RELIABLE_START_LOADING ); 1797 } 1798 } else { 1799 // If we are in a game lobby, and someone joins our party, tell them to join our game 1800 if ( lobbyType == TYPE_PARTY && sessionCB->GetState() == idSession::GAME_LOBBY ) { 1801 SendPeerMembersToLobby( peerNum, TYPE_GAME, false ); 1802 } 1803 } 1804 1805 // Send mic status of the current lobby to applicable peers 1806 SendPeersMicStatusToNewUsers( peerNum ); 1807 1808 // If we made is this far, update the users migration game data index 1809 for ( int u = 0; u < GetNumLobbyUsers(); u++ ) { 1810 if ( GetLobbyUser( u )->peerIndex == peerNum ) { 1811 GetLobbyUser( u )->migrationGameData = migrationGameData; 1812 } 1813 } 1814 1815 return peerNum; 1816 } 1817 1818 /* 1819 ======================== 1820 idLobby::InitStateLobbyHost 1821 ======================== 1822 */ 1823 void idLobby::InitStateLobbyHost() { 1824 assert( lobbyBackend != NULL ); 1825 1826 // We will be the host 1827 isHost = true; 1828 1829 if ( net_headlessServer.GetBool() ) { 1830 return; // Don't add any players to headless server 1831 } 1832 1833 if ( migrationInfo.state != MIGRATE_NONE ) { 1834 migrationInfo.persistUntilGameEndsData.wasMigratedHost = true; // InitSessionUsersFromLocalUsers needs to know this 1835 migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = false; 1836 // migrationDlg = GDM_MIGRATING_WAITING; 1837 } 1838 1839 // Initialize the initial user list for this lobby 1840 InitSessionUsersFromLocalUsers( MatchTypeIsOnline( parms.matchFlags ) ); 1841 1842 // Set the session's hostAddress to the local players' address. 1843 const int myUserIndex = GetLobbyUserIndexByLocalUserHandle( sessionCB->GetSignInManager().GetMasterLocalUserHandle() ); 1844 if ( myUserIndex != -1 ) { 1845 hostAddress = GetLobbyUser( myUserIndex )->address; 1846 } 1847 1848 // Since we are the host, we have to register our initial session users with the lobby 1849 // All additional users will join through AddUsersFromMsg, and RegisterUser is handled in there from here on out. 1850 // Peers will add users exclusively through AddUsersFromMsg. 1851 for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { 1852 lobbyUser_t * user = GetLobbyUser( i ); 1853 RegisterUser( user ); 1854 if ( lobbyType == TYPE_PARTY ) { 1855 user->partyToken = GetPartyTokenAsHost(); 1856 } 1857 } 1858 1859 // Set the lobbies skill level 1860 lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() ); 1861 1862 // Make sure and register all the addresses of the invites we'll send out as the new host 1863 if ( migrationInfo.state != MIGRATE_NONE ) { 1864 // Tell the session that we became the host, so the session mgr can adjust state if needed 1865 sessionCB->BecameHost( *this ); 1866 1867 // Register this address with this lobbyBackend 1868 for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) { 1869 lobbyBackend->RegisterAddress( migrationInfo.invites[i].address ); 1870 } 1871 } 1872 } 1873 1874 /* 1875 ======================== 1876 idLobby::SendMembersToLobby 1877 ======================== 1878 */ 1879 void idLobby::SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) { 1880 1881 // It's not our job to send party members to a game if we aren't the party host 1882 if ( !IsHost() ) { 1883 return; 1884 } 1885 1886 // Send the message to all connected peers 1887 for ( int i = 0; i < peers.Num(); i++ ) { 1888 if ( peers[ i ].IsConnected() ) { 1889 SendPeerMembersToLobby( i, destLobbyType, connectInfo, waitForOtherMembers ); 1890 } 1891 } 1892 } 1893 1894 /* 1895 ======================== 1896 idLobby::SendMembersToLobby 1897 ======================== 1898 */ 1899 void idLobby::SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ) { 1900 if ( destLobby.lobbyBackend == NULL ) { 1901 return; // We don't have a game lobbyBackend to get an address for 1902 } 1903 1904 lobbyConnectInfo_t connectInfo = destLobby.lobbyBackend->GetConnectInfo(); 1905 1906 SendMembersToLobby( destLobby.lobbyType, connectInfo, waitForOtherMembers ); 1907 } 1908 1909 /* 1910 ======================== 1911 idLobby::SendPeerMembersToLobby 1912 Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server. 1913 ======================== 1914 */ 1915 void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) { 1916 // It's not our job to send party members to a game if we aren't the party host 1917 if ( !IsHost() ) { 1918 return; 1919 } 1920 1921 assert( peerIndex >= 0 ); 1922 assert( peerIndex < peers.Num() ); 1923 peer_t & peer = peers[ peerIndex ]; 1924 1925 NET_VERBOSE_PRINT( "NET: Sending peer %i (%s) to game lobby\n", peerIndex, peer.address.ToString() ); 1926 1927 if ( !peer.IsConnected() ) { 1928 idLib::Warning( "NET: Can't send peer %i to game lobby: peer isn't in party", peerIndex ); 1929 return; 1930 } 1931 1932 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; 1933 idBitMsg outmsg( buffer, sizeof( buffer ) ); 1934 1935 // Have lobby fill out msg with connection info 1936 connectInfo.WriteToMsg( outmsg ); 1937 1938 outmsg.WriteByte( destLobbyType ); 1939 outmsg.WriteBool( waitForOtherMembers ); 1940 1941 QueueReliableMessage( peerIndex, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, outmsg.GetReadData(), outmsg.GetSize() ); 1942 } 1943 1944 /* 1945 ======================== 1946 idLobby::SendPeerMembersToLobby 1947 1948 Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server. 1949 ======================== 1950 */ 1951 void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ) { 1952 idLobby * lobby = sessionCB->GetLobbyFromType( destLobbyType ); 1953 1954 if ( !verify( lobby != NULL ) ) { 1955 return; 1956 } 1957 1958 if ( !verify( lobby->lobbyBackend != NULL ) ) { 1959 return; 1960 } 1961 1962 lobbyConnectInfo_t connectInfo = lobby->lobbyBackend->GetConnectInfo(); 1963 1964 SendPeerMembersToLobby( peerIndex, destLobbyType, connectInfo, waitForOtherMembers ); 1965 } 1966 1967 /* 1968 ======================== 1969 idLobby::NotifyPartyOfLeavingGameLobby 1970 ======================== 1971 */ 1972 void idLobby::NotifyPartyOfLeavingGameLobby() { 1973 if ( lobbyType != TYPE_PARTY ) { 1974 return; // We are not a party lobby 1975 } 1976 1977 if ( !IsHost() ) { 1978 return; // We are not the host of a party lobby, we can't do this 1979 } 1980 1981 if ( !( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) ) { 1982 return; // Options aren't set to notify party of leaving 1983 } 1984 1985 // Tell our party to leave the game they are in 1986 for ( int i = 0; i < peers.Num(); i++ ) { 1987 if ( peers[ i ].IsConnected() ) { 1988 QueueReliableMessage( i, RELIABLE_PARTY_LEAVE_GAME_LOBBY ); 1989 } 1990 } 1991 } 1992 1993 /* 1994 ======================== 1995 idLobby::GetPartyTokenAsHost 1996 ======================== 1997 */ 1998 uint32 idLobby::GetPartyTokenAsHost() { 1999 assert( lobbyType == TYPE_PARTY ); 2000 assert( IsHost() ); 2001 2002 if ( partyToken == 0 ) { 2003 // I don't know if this is mathematically sound, but it seems reasonable. 2004 // Don't do this at app startup (i.e. in the constructor) or it will be a lot less random. 2005 unsigned long seed = Sys_Milliseconds(); // time app has been running 2006 idLocalUser * masterUser = session->GetSignInManager().GetMasterLocalUser(); 2007 if ( masterUser != NULL ) { 2008 seed += idStr::Hash( masterUser->GetGamerTag() ); 2009 } 2010 partyToken = idRandom( seed ).RandomInt(); 2011 idLib::Printf( "NET: PartyToken is %u (seed = %u)\n", partyToken, seed ); 2012 } 2013 return partyToken; 2014 } 2015 2016 /* 2017 ======================== 2018 idLobby::EncodeSessionID 2019 ======================== 2020 */ 2021 idPacketProcessor::sessionId_t idLobby::EncodeSessionID( uint32 key ) const { 2022 assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) ); 2023 const int numBits = sizeof( idPacketProcessor::sessionId_t ) * 8 - idPacketProcessor::NUM_LOBBY_TYPE_BITS; 2024 const uint32 mask = ( 1 << numBits ) - 1; 2025 idPacketProcessor::sessionId_t sessionID = ( key & mask ) << idPacketProcessor::NUM_LOBBY_TYPE_BITS; 2026 sessionID |= ( lobbyType + 1 ); 2027 return sessionID; 2028 } 2029 2030 /* 2031 ======================== 2032 idLobby::EncodeSessionID 2033 ======================== 2034 */ 2035 void idLobby::DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const { 2036 assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) ); 2037 key = sessionID >> idPacketProcessor::NUM_LOBBY_TYPE_BITS; 2038 } 2039 2040 /* 2041 ======================== 2042 idLobby::GenerateSessionID 2043 ======================== 2044 */ 2045 idPacketProcessor::sessionId_t idLobby::GenerateSessionID() const { 2046 idPacketProcessor::sessionId_t sessionID = EncodeSessionID( Sys_Milliseconds() ); 2047 2048 // Make sure we can use it 2049 while ( !SessionIDCanBeUsedForInBand( sessionID ) ) { 2050 sessionID = IncrementSessionID( sessionID ); 2051 } 2052 2053 return sessionID; 2054 } 2055 2056 /* 2057 ======================== 2058 idLobby::SessionIDCanBeUsedForInBand 2059 ======================== 2060 */ 2061 bool idLobby::SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const { 2062 if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) { 2063 return false; 2064 } 2065 2066 if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ) { 2067 return false; 2068 } 2069 2070 if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ) { 2071 return false; 2072 } 2073 2074 if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ) { 2075 return false; 2076 } 2077 2078 return true; 2079 } 2080 2081 /* 2082 ======================== 2083 idLobby::IncrementSessionID 2084 ======================== 2085 */ 2086 idPacketProcessor::sessionId_t idLobby::IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const { 2087 // Increment, taking into account valid id's 2088 while ( 1 ) { 2089 uint32 key = 0; 2090 2091 DecodeSessionID( sessionID, key ); 2092 2093 key++; 2094 2095 sessionID = EncodeSessionID( key ); 2096 2097 if ( SessionIDCanBeUsedForInBand( sessionID ) ) { 2098 break; 2099 } 2100 } 2101 2102 return sessionID; 2103 } 2104 2105 #define VERIFY_CONNECTED_PEER( p, sessionType_, msgType ) \ 2106 if ( !verify( lobbyType == sessionType_ ) ) { \ 2107 idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ 2108 return; \ 2109 } \ 2110 if ( peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) { \ 2111 idLib::Printf( "NET: " #msgType ", peer:%s not connected for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ 2112 return; \ 2113 } 2114 2115 #define VERIFY_CONNECTING_PEER( p, sessionType_, msgType ) \ 2116 if ( !verify( lobbyType == sessionType_ ) ) { \ 2117 idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ 2118 return; \ 2119 } \ 2120 if ( peers[p].GetConnectionState() != CONNECTION_CONNECTING ) { \ 2121 idLib::Printf( "NET: " #msgType ", peer:%s not connecting for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \ 2122 return; \ 2123 } 2124 2125 #define VERIFY_FROM_HOST( p, sessionType_, msgType ) \ 2126 VERIFY_CONNECTED_PEER( p, sessionType_, msgType ); \ 2127 if ( p != host ) { \ 2128 idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \ 2129 return; \ 2130 } \ 2131 2132 #define VERIFY_FROM_CONNECTING_HOST( p, sessionType_, msgType ) \ 2133 VERIFY_CONNECTING_PEER( p, sessionType_, msgType ); \ 2134 if ( p != host ) { \ 2135 idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \ 2136 return; \ 2137 } \ 2138 2139 /* 2140 ======================== 2141 idLobby::HandleHelloAck 2142 ======================== 2143 */ 2144 void idLobby::HandleHelloAck( int p, idBitMsg & msg ) { 2145 peer_t & peer = peers[p]; 2146 2147 if ( state != STATE_CONNECT_HELLO_WAIT ) { 2148 idLib::Printf( "NET: Hello ack for session type %s while not waiting for hello.\n", GetLobbyName() ); 2149 SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore 2150 return; 2151 } 2152 if ( p != host ) { 2153 // This shouldn't be possible 2154 idLib::Printf( "NET: Hello ack for session type %s, not from correct host.\n", GetLobbyName() ); 2155 SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore 2156 return; 2157 } 2158 2159 assert( GetNumLobbyUsers() == 0 ); 2160 2161 NET_VERBOSE_PRINT( "NET: Hello ack for session type %s from %s\n", GetLobbyName(), peer.address.ToString() ); 2162 2163 // We are now connected to this session type 2164 SetPeerConnectionState( p, CONNECTION_ESTABLISHED ); 2165 2166 // Obtain what our peer index is on the host is 2167 peerIndexOnHost = msg.ReadLong(); 2168 2169 // If we connected to a party lobby, get the party token from the lobby owner 2170 if ( lobbyType == TYPE_PARTY ) { 2171 partyToken = msg.ReadLong(); 2172 } 2173 2174 // Read match parms 2175 parms.Read( msg ); 2176 2177 // Update lobbyBackend with parms 2178 if ( lobbyBackend != NULL ) { 2179 lobbyBackend->UpdateMatchParms( parms ); 2180 } 2181 2182 // Populate the user list with the one from the host (which will also include our local users) 2183 // This ensures the user lists are kept in sync 2184 FreeAllUsers(); 2185 AddUsersFromMsg( msg, p ); 2186 2187 // Make sure the host has a current heartbeat 2188 peer.lastHeartBeat = Sys_Milliseconds(); 2189 2190 lobbyBackend->PostConnectFromMsg( msg ); 2191 2192 // Tell the lobby controller to finalize the connection 2193 SetState( STATE_FINALIZE_CONNECT ); 2194 2195 // 2196 // Success - We've received an ack from the server, letting us know we've been registered with the lobbies 2197 // 2198 } 2199 2200 /* 2201 ======================== 2202 idLobby::GetLobbyUserName 2203 ======================== 2204 */ 2205 const char * idLobby::GetLobbyUserName( lobbyUserID_t lobbyUserID ) const { 2206 const int index = GetLobbyUserIndexByID( lobbyUserID ); 2207 const lobbyUser_t * user = GetLobbyUser( index ); 2208 2209 if ( user == NULL ) { 2210 for ( int i = 0; i < disconnectedUsers.Num(); i++ ) { 2211 if ( disconnectedUsers[i].lobbyUserID.CompareIgnoreLobbyType( lobbyUserID ) ) { 2212 return disconnectedUsers[i].gamertag; 2213 } 2214 } 2215 return INVALID_LOBBY_USER_NAME; 2216 } 2217 2218 return user->gamertag; 2219 } 2220 2221 /* 2222 ======================== 2223 idLobby::GetLobbyUserSkinIndex 2224 ======================== 2225 */ 2226 int idLobby::GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const { 2227 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2228 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2229 return user ? user->selectedSkin : 0; 2230 } 2231 2232 /* 2233 ======================== 2234 idLobby::GetLobbyUserWeaponAutoSwitch 2235 ======================== 2236 */ 2237 bool idLobby::GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const { 2238 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2239 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2240 return user ? user->weaponAutoSwitch : true; 2241 } 2242 2243 /* 2244 ======================== 2245 idLobby::GetLobbyUserWeaponAutoReload 2246 ======================== 2247 */ 2248 bool idLobby::GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const { 2249 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2250 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2251 return user ? user->weaponAutoReload: true; 2252 } 2253 2254 /* 2255 ======================== 2256 idLobby::GetLobbyUserLevel 2257 ======================== 2258 */ 2259 int idLobby::GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const { 2260 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2261 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2262 return user ? user->level : 0; 2263 } 2264 2265 /* 2266 ======================== 2267 idLobby::GetLobbyUserQoS 2268 ======================== 2269 */ 2270 int idLobby::GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const { 2271 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2272 2273 if ( IsHost() && IsSessionUserIndexLocal( userIndex ) ) { 2274 return 0; // Local users on the host of the active session have 0 ping 2275 } 2276 2277 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2278 2279 if ( !verify( user != NULL ) ) { 2280 return 0; 2281 } 2282 2283 return user->pingMs; 2284 } 2285 2286 /* 2287 ======================== 2288 idLobby::GetLobbyUserTeam 2289 ======================== 2290 */ 2291 int idLobby::GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const { 2292 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2293 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2294 return user ? user->teamNumber : 0; 2295 } 2296 2297 /* 2298 ======================== 2299 idLobby::SetLobbyUserTeam 2300 ======================== 2301 */ 2302 bool idLobby::SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) { 2303 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2304 lobbyUser_t * user = GetLobbyUser( userIndex ); 2305 2306 if ( user != NULL ) { 2307 if ( teamNumber != user->teamNumber ) { 2308 user->teamNumber = teamNumber; 2309 if ( IsHost() ) { 2310 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; 2311 idBitMsg msg( buffer, sizeof( buffer ) ); 2312 CreateUserUpdateMessage( userIndex, msg ); 2313 idBitMsg readMsg; 2314 readMsg.InitRead( buffer, msg.GetSize() ); 2315 UpdateSessionUserOnPeers( readMsg ); 2316 } 2317 return true; 2318 } 2319 } 2320 return false; 2321 } 2322 2323 /* 2324 ======================== 2325 idLobby::GetLobbyUserPartyToken 2326 ======================== 2327 */ 2328 int idLobby::GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const { 2329 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2330 const lobbyUser_t * user = GetLobbyUser( userIndex ); 2331 return user ? user->partyToken : 0; 2332 } 2333 2334 /* 2335 ======================== 2336 idLobby::GetProfileFromLobbyUser 2337 ======================== 2338 */ 2339 idPlayerProfile * idLobby::GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) { 2340 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2341 2342 idPlayerProfile * profile = NULL; 2343 2344 idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( userIndex ); 2345 2346 if ( localUser != NULL ) { 2347 profile = localUser->GetProfile(); 2348 } 2349 2350 if ( profile == NULL ) { 2351 // Whoops 2352 profile = session->GetSignInManager().GetDefaultProfile(); 2353 //idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." ); 2354 } 2355 2356 return profile; 2357 } 2358 2359 /* 2360 ======================== 2361 idLobby::GetLocalUserFromLobbyUser 2362 ======================== 2363 */ 2364 idLocalUser * idLobby::GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) { 2365 const int userIndex = GetLobbyUserIndexByID( lobbyUserID ); 2366 2367 return GetLocalUserFromLobbyUserIndex( userIndex ); 2368 } 2369 2370 /* 2371 ======================== 2372 idLobby::GetNumLobbyUsersOnTeam 2373 ======================== 2374 */ 2375 int idLobby::GetNumLobbyUsersOnTeam( int teamNumber ) const { 2376 int numTeam = 0; 2377 for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { 2378 if ( GetLobbyUser( i )->teamNumber == teamNumber ) { 2379 ++numTeam; 2380 } 2381 } 2382 return numTeam; 2383 } 2384 2385 /* 2386 ======================== 2387 idLobby::GetPeerName 2388 ======================== 2389 */ 2390 const char * idLobby::GetPeerName( int peerNum ) const { 2391 2392 for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { 2393 if ( !verify( GetLobbyUser( i ) != NULL ) ) { 2394 continue; 2395 } 2396 2397 if ( GetLobbyUser( i )->peerIndex == peerNum ) { 2398 return GetLobbyUserName( GetLobbyUser( i )->lobbyUserID ); 2399 } 2400 } 2401 2402 return INVALID_LOBBY_USER_NAME; 2403 } 2404 2405 /* 2406 ======================== 2407 idLobby::HandleReliableMsg 2408 ======================== 2409 */ 2410 void idLobby::HandleReliableMsg( int p, idBitMsg & msg ) { 2411 peer_t & peer = peers[p]; 2412 2413 int reliableType = msg.ReadByte(); 2414 2415 //idLib::Printf(" Received reliable msg: %i \n", reliableType ); 2416 2417 const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType(); 2418 2419 if ( reliableType == RELIABLE_HELLO ) { 2420 VERIFY_FROM_CONNECTING_HOST( p, lobbyType, RELIABLE_HELLO ); 2421 // This is sent from the host acking a request to join the game lobby 2422 HandleHelloAck( p, msg ); 2423 return; 2424 } else if ( reliableType == RELIABLE_USER_CONNECT_REQUEST ) { 2425 VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_USER_CONNECT_REQUEST ); 2426 2427 // This message is sent from a peer requesting for a new user to join the game lobby 2428 // This will be sent while we are in a game lobby as a host. otherwise, denied. 2429 NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECT_REQUEST (%s) from %s\n", GetLobbyName(), peer.address.ToString() ); 2430 2431 idSession::sessionState_t expectedState = ( lobbyType == TYPE_PARTY ) ? idSession::PARTY_LOBBY : idSession::GAME_LOBBY; 2432 2433 if ( sessionCB->GetState() == expectedState && IsHost() && NumFreeSlots() > 0 ) { // This assumes only one user in the msg 2434 // Add user to session, which will also forward the operation to all other peers 2435 AddUsersFromMsg( msg, p ); 2436 } else { 2437 // Let peer know user couldn't be added 2438 HandleUserConnectFailure( p, msg, RELIABLE_USER_CONNECT_DENIED ); 2439 } 2440 } else if ( reliableType == RELIABLE_USER_CONNECT_DENIED ) { 2441 // This message is sent back from the host when a RELIABLE_PARTY_USER_CONNECT_REQUEST failed 2442 VERIFY_FROM_HOST( p, lobbyType, RELIABLE_PARTY_USER_CONNECT_DENIED ); 2443 2444 // Remove this user from the sign-in manager, so we don't keep trying to add them 2445 if ( !sessionCB->GetSignInManager().RemoveLocalUserByHandle( localUserHandle_t( msg.ReadLong() ) ) ) { 2446 NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_USER_CONNECT_DENIED, local user not found\n" ); 2447 return; 2448 } 2449 } else if ( reliableType == RELIABLE_KICK_PLAYER ) { 2450 VERIFY_FROM_HOST( p, lobbyType, RELIABLE_KICK_PLAYER ); 2451 common->Dialog().AddDialog( GDM_KICKED, DIALOG_ACCEPT, NULL, NULL, false ); 2452 if ( sessionCB->GetPartyLobby().IsHost() ) { 2453 session->SetSessionOption( idSession::OPTION_LEAVE_WITH_PARTY ); 2454 } 2455 session->Cancel(); 2456 } else if ( reliableType == RELIABLE_HEADSET_STATE ) { 2457 HandleHeadsetStateChange( p, msg ); 2458 } else if ( reliableType == RELIABLE_USER_CONNECTED ) { 2459 // This message is sent back from the host when users have connected, and we need to update our lists to reflect that 2460 VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_CONNECTED ); 2461 2462 NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECTED (%s) from %s\n", GetLobbyName(), peer.address.ToString() ); 2463 AddUsersFromMsg( msg, p ); 2464 } else if ( reliableType == RELIABLE_USER_DISCONNECTED ) { 2465 // This message is sent back from the host when users have diconnected, and we need to update our lists to reflect that 2466 VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_DISCONNECTED ); 2467 2468 ProcessUserDisconnectMsg( msg ); 2469 } else if ( reliableType == RELIABLE_MATCH_PARMS ) { 2470 parms.Read( msg ); 2471 // Update lobby with parms 2472 if ( lobbyBackend != NULL ) { 2473 lobbyBackend->UpdateMatchParms( parms ); 2474 } 2475 } else if ( reliableType == RELIABLE_START_LOADING ) { 2476 // This message is sent from the host to start loading a map 2477 VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_START_LOADING ); 2478 2479 NET_VERBOSE_PRINT( "NET: RELIABLE_START_LOADING from %s\n", peer.address.ToString() ); 2480 2481 startLoadingFromHost = true; 2482 } else if ( reliableType == RELIABLE_LOADING_DONE ) { 2483 // This message is sent from the peers to state they are done loading the map 2484 VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_LOADING_DONE ); 2485 2486 unsigned long networkChecksum = 0; 2487 networkChecksum = msg.ReadLong(); 2488 2489 peer.networkChecksum = networkChecksum; 2490 peer.loaded = true; 2491 } else if ( reliableType == RELIABLE_IN_GAME ) { 2492 VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_IN_GAME ); 2493 2494 peer.inGame = true; 2495 } else if ( reliableType == RELIABLE_SNAPSHOT_ACK ) { 2496 VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_SNAPSHOT_ACK ); 2497 2498 // update our base state for his last received snapshot 2499 int snapNum = msg.ReadLong(); 2500 float receivedBps = msg.ReadQuantizedUFloat< BANDWIDTH_REPORTING_MAX, BANDWIDTH_REPORTING_BITS >(); 2501 2502 // Update reported received bps 2503 if ( peer.receivedBpsIndex != snapNum ) { 2504 // Only do this the first time we get reported bps per snapshot. Subsequent ACKs of the same shot will usually have lower reported bps 2505 // due to more time elapsing but not receiving a new ss 2506 peer.receivedBps = receivedBps; 2507 peer.receivedBpsIndex = snapNum; 2508 } 2509 2510 ApplySnapshotDelta( p, snapNum ); 2511 2512 //idLib::Printf( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ); 2513 NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ) ); 2514 2515 } else if ( reliableType == RELIABLE_RESOURCE_ACK ) { 2516 } else if ( reliableType == RELIABLE_UPDATE_MATCH_PARMS ) { 2517 VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_UPDATE_MATCH_PARMS ); 2518 int msgType = msg.ReadLong(); 2519 sessionCB->HandlePeerMatchParamUpdate( p, msgType ); 2520 2521 } else if ( reliableType == RELIABLE_MATCHFINISHED ) { 2522 VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_MATCHFINISHED ); 2523 2524 sessionCB->ClearMigrationState(); 2525 2526 } else if ( reliableType == RELIABLE_ENDMATCH ) { 2527 VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH ); 2528 2529 sessionCB->EndMatchInternal(); 2530 2531 } else if ( reliableType == RELIABLE_ENDMATCH_PREMATURE ) { 2532 VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH_PREMATURE ); 2533 2534 sessionCB->EndMatchInternal( true ); 2535 2536 } else if ( reliableType == RELIABLE_START_MATCH_GAME_LOBBY_HOST ) { 2537 // This message should be from the host of the game lobby, telling us (as the host of the GameStateLobby) to start loading 2538 VERIFY_CONNECTED_PEER( p, TYPE_GAME_STATE, RELIABLE_START_MATCH_GAME_LOBBY_HOST ); 2539 2540 if ( session->GetState() >= idSession::LOADING ) { 2541 NET_VERBOSE_PRINT( "NET: RELIABLE_START_MATCH_GAME_LOBBY_HOST already loading\n" ); 2542 return; 2543 } 2544 2545 // Read match parms, and start loading 2546 parms.Read( msg ); 2547 2548 // Send these new match parms to currently connected peers 2549 SendMatchParmsToPeers(); 2550 2551 startLoadingFromHost = true; // Hijack this flag 2552 } else if ( reliableType == RELIABLE_ARBITRATE ) { 2553 VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE ); 2554 // Host telling us to arbitrate 2555 // 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 2556 respondToArbitrate = true; 2557 } else if ( reliableType == RELIABLE_ARBITRATE_OK ) { 2558 VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE_OK ); 2559 2560 NET_VERBOSE_PRINT( "NET: Got an arbitration ok from %d\n", p ); 2561 2562 everyoneArbitrated = true; 2563 for ( int i = 0; i < GetNumLobbyUsers(); i++ ) { 2564 lobbyUser_t * user = GetLobbyUser( i ); 2565 if ( !verify( user != NULL ) ) { 2566 continue; 2567 } 2568 if ( user->peerIndex == p ) { 2569 user->arbitrationAcked = true; 2570 } else if ( !user->arbitrationAcked ) { 2571 everyoneArbitrated = false; 2572 } 2573 } 2574 2575 if ( everyoneArbitrated ) { 2576 NET_VERBOSE_PRINT( "NET: Everyone says they registered for arbitration, verifying\n" ); 2577 lobbyBackend->Arbitrate(); 2578 //sessionCB->EveryoneArbitrated(); 2579 return; 2580 } 2581 } else if ( reliableType == RELIABLE_POST_STATS ) { 2582 VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_POST_STATS ); 2583 sessionCB->RecvLeaderboardStats( msg ); 2584 } else if ( reliableType == RELIABLE_SESSION_USER_MODIFIED ) { 2585 VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_SESSION_USER_MODIFIED ); 2586 UpdateSessionUserOnPeers( msg ); 2587 2588 } else if ( reliableType == RELIABLE_UPDATE_SESSION_USER ) { 2589 VERIFY_FROM_HOST( p, lobbyType, RELIABLE_UPDATE_SESSION_USER ); 2590 HandleUpdateSessionUser( msg ); 2591 } else if ( reliableType == RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ) { 2592 VERIFY_FROM_HOST( p, lobbyType, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ); 2593 2594 NET_VERBOSE_PRINT( "NET: RELIABLE_CONNECT_AND_MOVE_TO_LOBBY\n" ); 2595 2596 if ( IsHost() ) { 2597 idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: We are the host.\n" ); 2598 return; 2599 } 2600 2601 // Get connection info 2602 lobbyConnectInfo_t connectInfo; 2603 connectInfo.ReadFromMsg( msg ); 2604 2605 const lobbyType_t destLobbyType = (lobbyType_t)msg.ReadByte(); 2606 const bool waitForMembers = msg.ReadBool(); 2607 2608 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) 2609 2610 sessionCB->ConnectAndMoveToLobby( destLobbyType, connectInfo, waitForMembers ); 2611 } else if ( reliableType == RELIABLE_PARTY_CONNECT_OK ) { 2612 VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_CONNECT_OK ); 2613 if ( !sessionCB->GetGameLobby().waitForPartyOk ) { 2614 idLib::Printf( "RELIABLE_PARTY_CONNECT_OK: Wasn't waiting for ok.\n" ); 2615 } 2616 sessionCB->GetGameLobby().waitForPartyOk = false; 2617 } else if ( reliableType == RELIABLE_PARTY_LEAVE_GAME_LOBBY ) { 2618 VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_LEAVE_GAME_LOBBY ); 2619 2620 NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_LEAVE_GAME_LOBBY\n" ); 2621 2622 if ( sessionCB->GetState() != idSession::GAME_LOBBY ) { 2623 idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Not in a game lobby, ignoring.\n" ); 2624 return; 2625 } 2626 2627 if ( IsHost() ) { 2628 idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Host of party, ignoring.\n" ); 2629 return; 2630 } 2631 2632 sessionCB->LeaveGameLobby(); 2633 } else if ( IsReliablePlayerToPlayerType( reliableType ) ) { 2634 HandleReliablePlayerToPlayerMsg( p, msg, reliableType ); 2635 } else if ( reliableType == RELIABLE_PING ) { 2636 HandleReliablePing( p, msg ); 2637 } else if ( reliableType == RELIABLE_PING_VALUES ) { 2638 HandlePingValues( msg ); 2639 } else if ( reliableType == RELIABLE_BANDWIDTH_VALUES ) { 2640 HandleBandwidhTestValue( p, msg ); 2641 } else if ( reliableType == RELIABLE_MIGRATION_GAME_DATA ) { 2642 HandleMigrationGameData( msg ); 2643 } else if ( reliableType >= RELIABLE_GAME_DATA ) { 2644 2645 VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_GAME_DATA ); 2646 2647 common->NetReceiveReliable( p, reliableType - RELIABLE_GAME_DATA, msg ); 2648 } else if ( reliableType == RELIABLE_DUMMY_MSG ) { 2649 // Ignore dummy msg's 2650 NET_VERBOSE_PRINT( "NET: ignoring dummy msg from %s\n", peer.address.ToString() ); 2651 } else { 2652 NET_VERBOSE_PRINT( "NET: Unknown reliable packet type %d from %s\n", reliableType, peer.address.ToString() ); 2653 } 2654 } 2655 2656 /* 2657 ======================== 2658 idLobby::GetTotalOutgoingRate 2659 ======================== 2660 */ 2661 int idLobby::GetTotalOutgoingRate() { 2662 int totalSendRate = 0; 2663 for ( int p = 0; p < peers.Num(); p++ ) { 2664 const peer_t & peer = peers[p]; 2665 2666 if ( !peer.IsConnected() ) { 2667 continue; 2668 } 2669 2670 const idPacketProcessor & proc = *peer.packetProc; 2671 2672 totalSendRate += proc.GetOutgoingRateBytes(); 2673 } 2674 return totalSendRate; 2675 } 2676 2677 /* 2678 ======================== 2679 idLobby::DrawDebugNetworkHUD 2680 ======================== 2681 */ 2682 extern idCVar net_forceUpstream; 2683 void idLobby::DrawDebugNetworkHUD() const { 2684 int totalSendRate = 0; 2685 int totalRecvRate = 0; 2686 float totalSentMB = 0.0f; 2687 float totalRecvMB = 0.0f; 2688 2689 const float Y_OFFSET = 20.0f; 2690 const float X_OFFSET = 20.0f; 2691 const float Y_SPACING = 15.0f; 2692 2693 float curY = Y_OFFSET; 2694 2695 int numLines = ( net_forceUpstream.GetFloat() != 0.0f ? 6: 5 ); 2696 2697 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 ); 2698 2699 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 ); 2700 curY += Y_SPACING; 2701 2702 renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false ); 2703 curY += Y_SPACING; 2704 2705 for ( int p = 0; p < peers.Num(); p++ ) { 2706 const peer_t & peer = peers[p]; 2707 2708 if ( !peer.IsConnected() ) { 2709 continue; 2710 } 2711 2712 const idPacketProcessor & proc = *peer.packetProc; 2713 2714 totalSendRate += proc.GetOutgoingRateBytes(); 2715 totalRecvRate += proc.GetIncomingRateBytes(); 2716 float sentKps = (float)proc.GetOutgoingRateBytes() / 1024.0f; 2717 float recvKps = (float)proc.GetIncomingRateBytes() / 1024.0f; 2718 float sentMB = (float)proc.GetOutgoingBytes() / ( 1024.0f * 1024.0f ); 2719 float recvMB = (float)proc.GetIncomingBytes() / ( 1024.0f * 1024.0f ); 2720 2721 totalSentMB += sentMB; 2722 totalRecvMB += recvMB; 2723 2724 idVec4 color = sentKps > 20.0f ? colorRed : colorGreen; 2725 2726 int resourcePercent = 0; 2727 idStr name = peer.address.ToString(); 2728 2729 name += lobbyType == TYPE_PARTY ? "(P": "(G"; 2730 name += host == p ? ":H)" : ":C)"; 2731 2732 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 ); 2733 curY += Y_SPACING; 2734 } 2735 2736 renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false ); 2737 curY += Y_SPACING; 2738 2739 float totalSentKps = (float)totalSendRate / 1024.0f; 2740 float totalRecvKps = (float)totalRecvRate / 1024.0f; 2741 2742 idVec4 color = totalSentKps > 100.0f ? colorRed : colorGreen; 2743 2744 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 ); 2745 curY += Y_SPACING; 2746 2747 if ( net_forceUpstream.GetFloat() != 0.0f ) { 2748 float upstreamDropRate = session->GetUpstreamDropRate(); 2749 float upstreamQueuedRate = session->GetUpstreamQueueRate(); 2750 2751 int queuedBytes = session->GetQueuedBytes(); 2752 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 ); 2753 } 2754 } 2755 2756 /* 2757 ======================== 2758 idLobby::DrawDebugNetworkHUD2 2759 ======================== 2760 */ 2761 void idLobby::DrawDebugNetworkHUD2() const { 2762 int totalSendRate = 0; 2763 int totalRecvRate = 0; 2764 2765 const float Y_OFFSET = 20.0f; 2766 const float X_OFFSET = 20.0f; 2767 const float Y_SPACING = 15.0f; 2768 2769 float curY = Y_OFFSET; 2770 2771 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 ); 2772 2773 const char* stateName = session->GetStateString(); 2774 2775 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 ); 2776 2777 renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), va("State: %s. Local time: %d", stateName, Sys_Milliseconds() ), colorGreen, false ); 2778 curY += Y_SPACING; 2779 2780 renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "Peer | Sent kB/s | Recv kB/s | L | R | Resources", colorGreen, false ); 2781 curY += Y_SPACING; 2782 2783 renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false ); 2784 curY += Y_SPACING; 2785 2786 for ( int p = 0; p < peers.Num(); p++ ) { 2787 2788 if ( !peers[ p ].IsConnected() ) { 2789 continue; 2790 } 2791 2792 idPacketProcessor & proc = *peers[ p ].packetProc; 2793 2794 totalSendRate += proc.GetOutgoingRate2(); 2795 totalRecvRate += proc.GetIncomingRate2(); 2796 float sentKps = ( float )proc.GetOutgoingRate2() / 1024.0f; 2797 float recvKps = ( float )proc.GetIncomingRate2() / 1024.0f; 2798 2799 // should probably complement that with a bandwidth reading 2800 // right now I am mostly concerned about fragmentation and the latency spikes it will cause 2801 idVec4 color = proc.TickFragmentAccumulator() ? colorRed : colorGreen; 2802 2803 int rLoaded = peers[ p ].numResources; 2804 int rTotal = 0; 2805 2806 // show the names of the clients connected to the server. Also make sure it looks reasonably good. 2807 idStr peerName; 2808 if ( IsHost() ) { 2809 peerName = GetPeerName( p ); 2810 2811 int MAX_PEERNAME_LENGTH = 10; 2812 int nameLength = peerName.Length(); 2813 if ( nameLength > MAX_PEERNAME_LENGTH ) { 2814 peerName = peerName.Left( MAX_PEERNAME_LENGTH ); 2815 } else if ( nameLength < MAX_PEERNAME_LENGTH ) { 2816 idStr filler; 2817 filler.Fill( ' ', MAX_PEERNAME_LENGTH ); 2818 peerName += filler.Left( MAX_PEERNAME_LENGTH - nameLength ); 2819 } 2820 } else { 2821 peerName = "Local "; 2822 } 2823 2824 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 ); 2825 curY += Y_SPACING; 2826 } 2827 2828 renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false ); 2829 curY += Y_SPACING; 2830 2831 float totalSentKps = (float)totalSendRate / 1024.0f; 2832 float totalRecvKps = (float)totalRecvRate / 1024.0f; 2833 2834 renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Total | %2.02f KB/s | %2.02f KB/s", totalSentKps, totalRecvKps ), colorGreen, false ); 2835 } 2836 2837 2838 /* 2839 ======================== 2840 idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics 2841 ======================== 2842 */ 2843 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" ); 2844 void idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) { 2845 const float Y_OFFSET = 20.0f; 2846 const float X_OFFSET = 20.0f; 2847 const float Y_SPACING = 15.0f; 2848 idVec4 color = colorWhite; 2849 2850 float curY = Y_OFFSET; 2851 2852 if ( !draw ) { 2853 for ( int p=0; p < peers.Num(); p++ ) { 2854 for ( int i=0; i < peers[p].debugGraphs.Num(); i++ ) { 2855 if ( peers[p].debugGraphs[i] != NULL ) { 2856 peers[p].debugGraphs[i]->Enable( false ); 2857 } else { 2858 return; 2859 } 2860 } 2861 } 2862 return; 2863 } 2864 2865 static int lastTime = 0; 2866 int time = Sys_Milliseconds(); 2867 2868 for ( int p = 0; p < peers.Num(); p++ ) { 2869 2870 peer_t & peer = peers[p]; 2871 2872 if ( !peer.IsConnected() ) { 2873 continue; 2874 } 2875 2876 idPacketProcessor * packetProc = peer.packetProc; 2877 idSnapshotProcessor * snapProc = peer.snapProc; 2878 2879 if ( !verify( packetProc != NULL && snapProc != NULL ) ) { 2880 continue; 2881 } 2882 2883 int snapSeq = snapProc->GetSnapSequence(); 2884 int snapBase = snapProc->GetBaseSequence(); 2885 int deltaSeq = snapSeq - snapBase; 2886 bool throttled = peer.throttledSnapRate > common->GetSnapRate(); 2887 2888 int numLines = net_forceUpstream.GetBool() ? 5 : 4; 2889 2890 const int width = renderSystem->GetWidth()/2.0f - (X_OFFSET * 2); 2891 2892 enum netDebugGraphs_t { 2893 GRAPH_SNAPSENT, 2894 GRAPH_OUTGOING, 2895 GRAPH_INCOMINGREPORTED, 2896 GRAPH_MAX 2897 }; 2898 2899 peer.debugGraphs.SetNum( GRAPH_MAX, NULL ); 2900 for ( int i=0; i < GRAPH_MAX; i++ ) { 2901 // Initialize graphs 2902 if ( peer.debugGraphs[i] == NULL ) { 2903 peer.debugGraphs[i] = console->CreateGraph( 500 ); 2904 if ( !verify( peer.debugGraphs[i] != NULL ) ) { 2905 continue; 2906 } 2907 2908 peer.debugGraphs[i]->SetPosition( X_OFFSET - 10.0f + width, curY - 10.0f, width , Y_SPACING * numLines ); 2909 } 2910 2911 peer.debugGraphs[i]->Enable( true ); 2912 } 2913 2914 renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, width, ( Y_SPACING * numLines ) + 20.0f ); 2915 2916 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 ); 2917 curY += Y_SPACING; 2918 2919 renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "SnapSeq %d BaseSeq %d Delta %d Queue %d", snapSeq, snapBase, deltaSeq, snapProc->GetSnapQueueSize() ), color, false ); 2920 curY += Y_SPACING; 2921 2922 renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Reliables: %d / %d bytes Reliable Ack: %d", packetProc->NumQueuedReliables(), packetProc->GetReliableDataSize(), packetProc->NeedToSendReliableAck() ), color, false ); 2923 curY += Y_SPACING; 2924 2925 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 ); 2926 curY += Y_SPACING; 2927 2928 if ( net_forceUpstream.GetFloat() != 0.0f ) { 2929 float upstreamDropRate = session->GetUpstreamDropRate(); 2930 float upstreamQueuedRate = session->GetUpstreamQueueRate(); 2931 int queuedBytes = session->GetQueuedBytes(); 2932 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 ); 2933 2934 } 2935 2936 curY += Y_SPACING; 2937 2938 2939 2940 if ( peer.debugGraphs[GRAPH_SNAPSENT] != NULL ) { 2941 if ( peer.lastSnapTime > lastTime ) { 2942 peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 1.0f, colorBlue ); 2943 } else { 2944 peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 0.0f, colorBlue ); 2945 } 2946 } 2947 2948 if ( peer.debugGraphs[GRAPH_OUTGOING] != NULL ) { 2949 idVec4 bgColor( vec4_zero ); 2950 peer.debugGraphs[GRAPH_OUTGOING]->SetBackgroundColor( bgColor ); 2951 2952 idVec4 lineColor = colorLtGrey; 2953 lineColor.w = 0.5f; 2954 float outgoingRate = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ]; 2955 // peer.packetProc->GetOutgoingRateBytes() 2956 peer.debugGraphs[GRAPH_OUTGOING]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, outgoingRate / net_debughud3_bps_max.GetFloat() ), lineColor ); 2957 } 2958 2959 2960 if ( peer.debugGraphs[GRAPH_INCOMINGREPORTED] != NULL ) { 2961 idVec4 lineColor = colorYellow; 2962 extern idCVar net_peer_throttle_bps_peer_threshold_pct; 2963 extern idCVar net_peer_throttle_bps_host_threshold; 2964 2965 if ( peer.packetProc->GetOutgoingRateBytes() > net_peer_throttle_bps_host_threshold.GetFloat() ) { 2966 float pct = peer.packetProc->GetOutgoingRateBytes() > 0.0f ? peer.receivedBps / peer.packetProc->GetOutgoingRateBytes() : 0.0f; 2967 if ( pct < net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) { 2968 lineColor = colorRed; 2969 } else { 2970 lineColor = colorGreen; 2971 } 2972 } 2973 idVec4 bgColor( vec4_zero ); 2974 peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetBackgroundColor( bgColor ); 2975 peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetFillMode( idDebugGraph::GRAPH_LINE ); 2976 peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, peer.receivedBps / net_debughud3_bps_max.GetFloat() ), lineColor ); 2977 } 2978 2979 2980 2981 // Skip down 2982 curY += ( Y_SPACING * 2.0f); 2983 } 2984 2985 lastTime = time; 2986 } 2987 2988 /* 2989 ======================== 2990 idLobby::CheckHeartBeats 2991 ======================== 2992 */ 2993 void idLobby::CheckHeartBeats() { 2994 // Disconnect peers that haven't responded within net_peerTimeoutInSeconds 2995 int time = Sys_Milliseconds(); 2996 2997 int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000; 2998 2999 if ( sessionCB->GetState() < idSession::LOADING && migrationInfo.state == MIGRATE_NONE ) { 3000 // Use shorter timeout in lobby (TCR) 3001 timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds_Lobby", net_peerTimeoutInSeconds_Lobby.GetInteger() ) * 1000; 3002 } 3003 3004 if ( timeoutInMs > 0 ) { 3005 for ( int p = 0; p < peers.Num(); p++ ) { 3006 if ( peers[p].IsConnected() ) { 3007 3008 bool peerTimeout = false; 3009 3010 if( time - peers[p].lastHeartBeat > timeoutInMs ) { 3011 peerTimeout = true; 3012 } 3013 3014 // if reliable queue is almost full, disconnect the peer. 3015 // (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never 3016 // have more than 3 or 4 queued) 3017 if ( peers[ p ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE - 1 ) { 3018 peerTimeout = true; 3019 } 3020 3021 if( peerTimeout ) { 3022 // Disconnect the peer from any sessions we are a host of 3023 if ( IsHost() ) { 3024 idLib::Printf("Peer %i timed out for %s session @ %d (lastHeartBeat %d)\n", p, GetLobbyName(), time, peers[p].lastHeartBeat ); 3025 DisconnectPeerFromSession( p ); 3026 } 3027 3028 // Handle peers not receiving a heartbeat from the host in awhile 3029 if ( IsPeer() ) { 3030 if ( migrationInfo.state != MIGRATE_PICKING_HOST ) { 3031 idLib::Printf("Host timed out for %s session\n", GetLobbyName() ); 3032 3033 // Pick a host for this session 3034 PickNewHost(); 3035 } 3036 } 3037 } 3038 } 3039 } 3040 } 3041 3042 if ( IsHost() && lobbyType == GetActingGameStateLobbyType() ) { 3043 for ( int p = 0; p < peers.Num(); p++ ) { 3044 if ( !peers[p].IsConnected() ) { 3045 continue; 3046 } 3047 3048 CheckPeerThrottle( p ); 3049 } 3050 } 3051 } 3052 3053 /* 3054 ======================== 3055 idLobby::CheckHeartBeats 3056 ======================== 3057 */ 3058 bool idLobby::IsLosingConnectionToHost() const { 3059 if ( !verify( IsPeer() && host >= 0 && host < peers.Num() ) ) { 3060 return false; 3061 } 3062 3063 if ( !peers[ host ].IsConnected() ) { 3064 return true; 3065 } 3066 3067 int time = Sys_Milliseconds(); 3068 3069 int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000; 3070 3071 // return true if heartbeat > half the timeout length 3072 if ( timeoutInMs > 0 && time - peers[ host ].lastHeartBeat > timeoutInMs / 2 ) { 3073 return true; 3074 } 3075 3076 // return true if reliable queue is more than half full 3077 // (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never 3078 // have more than 3 or 4 queued) 3079 if ( peers[ host ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) { 3080 return true; 3081 } 3082 3083 return false; 3084 } 3085 3086 /* 3087 ======================== 3088 idLobby::IsMigratedStatsGame 3089 ======================== 3090 */ 3091 bool idLobby::IsMigratedStatsGame() const { 3092 if ( !IsLobbyActive() ) { 3093 return false; 3094 } 3095 3096 if ( lobbyType != TYPE_GAME ) { 3097 return false; // Only game session migrates games stats 3098 } 3099 3100 if ( !MatchTypeHasStats( parms.matchFlags ) ) { 3101 return false; // Only stats games migrate stats 3102 } 3103 3104 if ( !MatchTypeIsRanked( parms.matchFlags ) ) { 3105 return false; // Only ranked games should migrate stats into new game 3106 } 3107 3108 return migrationInfo.persistUntilGameEndsData.wasMigratedGame && migrationInfo.persistUntilGameEndsData.hasGameData; 3109 } 3110 3111 /* 3112 ======================== 3113 idLobby::ShouldRelaunchMigrationGame 3114 returns true if we are hosting a migrated game and we had valid migration data 3115 ======================== 3116 */ 3117 bool idLobby::ShouldRelaunchMigrationGame() const { 3118 if ( IsMigrating() ) { 3119 return false; // Don't relaunch until all clients have reconnected 3120 } 3121 3122 if ( !IsMigratedStatsGame() ) { 3123 return false; // If we are not migrating stats, we don't want to relaunch a new game 3124 } 3125 3126 if ( !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) { 3127 return false; // Only relaunch if we are the host 3128 } 3129 3130 if ( migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame ) { 3131 return false; // We already relaunched this game 3132 } 3133 3134 return true; 3135 } 3136 3137 /* 3138 ======================== 3139 idLobby::ShouldShowMigratingDialog 3140 ======================== 3141 */ 3142 bool idLobby::ShouldShowMigratingDialog() const { 3143 if ( IsMigrating() ) { 3144 return true; // If we are in the process of truly migrating, then definitely return true 3145 } 3146 3147 if ( sessionCB->GetState() == idSession::INGAME ) { 3148 return false; 3149 } 3150 3151 // We're either waiting on the server (which could be us) to relaunch, so show the dialog 3152 return IsMigratedStatsGame() && sessionCB->GetState() != idSession::INGAME; 3153 } 3154 3155 /* 3156 ======================== 3157 idLobby::IsMigrating 3158 ======================== 3159 */ 3160 bool idLobby::IsMigrating() const { 3161 return migrationInfo.state != idLobby::MIGRATE_NONE; 3162 } 3163 3164 /* 3165 ======================== 3166 idLobby::PingPeers 3167 Host only. 3168 ======================== 3169 */ 3170 void idLobby::PingPeers() { 3171 if ( !verify( IsHost() )) { 3172 return; 3173 } 3174 3175 const int now = Sys_Milliseconds(); 3176 3177 pktPing_t packet; 3178 memset( &packet, 0, sizeof( packet ) ); // We're gonna memset like it's 1999. 3179 packet.timestamp = now; 3180 3181 byte packetCopy[ sizeof( packet ) ]; 3182 idBitMsg msg( packetCopy, sizeof( packetCopy ) ); 3183 msg.WriteLong( packet.timestamp ); 3184 3185 for ( int i = 0; i < peers.Num(); ++i ) { 3186 peer_t & peer = peers[ i ]; 3187 if ( !peer.IsConnected() ) { 3188 continue; 3189 } 3190 if ( peer.nextPing <= now ) { 3191 peer.nextPing = now + PING_INTERVAL_MS; 3192 QueueReliableMessage( i, RELIABLE_PING, msg.GetReadData(), msg.GetSize() ); 3193 } 3194 } 3195 } 3196 3197 /* 3198 ======================== 3199 idLobby::ThrottlePeerSnapRate 3200 ======================== 3201 */ 3202 void idLobby::ThrottlePeerSnapRate( int p ) { 3203 if ( !verify( IsHost() ) || !verify( p >= 0 ) ) { 3204 return; 3205 } 3206 3207 peers[p].throttledSnapRate = common->GetSnapRate() * 2; 3208 idLib::Printf( "^1Throttling peer %d %s!\n", p, GetPeerName(p) ); 3209 idLib::Printf( " New snaprate: %d\n", peers[p].throttledSnapRate / 1000 ); 3210 } 3211 3212 /* 3213 ======================== 3214 idLobby::SaturatePeers 3215 ======================== 3216 */ 3217 void idLobby::BeginBandwidthTest() { 3218 if ( !verify( IsHost() ) ) { 3219 idLib::Warning("Bandwidth test should only be done on host"); 3220 return; 3221 } 3222 3223 if ( bandwidthChallengeStartTime > 0 ) { 3224 idLib::Warning("Already started bandwidth test"); 3225 return; 3226 } 3227 3228 int time = Sys_Milliseconds(); 3229 bandwidthChallengeStartTime = time; 3230 bandwidthChallengeEndTime = 0; 3231 bandwidthChallengeFinished = false; 3232 bandwidthChallengeNumGoodSeq = 0; 3233 3234 for ( int p = 0; p < peers.Num(); ++p ) { 3235 if ( !peers[ p ].IsConnected() ) { 3236 continue; 3237 } 3238 3239 if ( !verify( peers[ p ].packetProc != NULL ) ) { 3240 continue; 3241 } 3242 3243 peers[ p ].bandwidthSequenceNum = 0; 3244 peers[ p ].bandwidthChallengeStartSendTime = 0; 3245 peers[ p ].bandwidthChallengeResults = false; 3246 peers[ p ].bandwidthChallengeSendComplete = false; 3247 peers[ p ].bandwidthTestBytes = peers[ p ].packetProc->GetOutgoingBytes(); // cache this off so we can see the difference when we are done 3248 } 3249 } 3250 3251 /* 3252 ======================== 3253 idLobby::SaturatePeers 3254 ======================== 3255 */ 3256 bool idLobby::BandwidthTestStarted() { 3257 return bandwidthChallengeStartTime != 0; 3258 } 3259 /* 3260 ======================== 3261 idLobby::ServerUpdateBandwidthTest 3262 ======================== 3263 */ 3264 void idLobby::ServerUpdateBandwidthTest() { 3265 if ( bandwidthChallengeStartTime <= 0 ) { 3266 // Not doing a test 3267 return; 3268 } 3269 3270 if ( !verify( IsHost() )) { 3271 return; 3272 } 3273 3274 int time = Sys_Milliseconds(); 3275 3276 if ( bandwidthChallengeFinished ) { 3277 // test is over 3278 return; 3279 } 3280 3281 idRandom random; 3282 random.SetSeed( time ); 3283 3284 bool sentAll = true; 3285 bool recAll = true; 3286 3287 for ( int i = 0; i < peers.Num(); ++i ) { 3288 peer_t & peer = peers[ i ]; 3289 if ( !peer.IsConnected() ) { 3290 continue; 3291 } 3292 3293 if ( peer.bandwidthChallengeResults ) { 3294 continue; 3295 } 3296 recAll = false; 3297 3298 if ( peer.bandwidthChallengeSendComplete ) { 3299 continue; 3300 } 3301 sentAll = false; 3302 3303 if ( time - peer.bandwidthTestLastSendTime < session->GetTitleStorageInt( "net_bw_test_interval", net_bw_test_interval.GetInteger() ) ) { 3304 continue; 3305 } 3306 3307 if ( peer.packetProc->HasMoreFragments() ) { 3308 continue; 3309 } 3310 3311 if ( peer.bandwidthChallengeStartSendTime == 0 ) { 3312 peer.bandwidthChallengeStartSendTime = time; 3313 } 3314 3315 peer.bandwidthTestLastSendTime = time; 3316 3317 // Ok, send him a big packet 3318 byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; // <---- NOTE - When calling ProcessOutgoingMsg with true for oob, we can't go over this size 3319 idBitMsg msg( buffer, sizeof(buffer) ); 3320 3321 msg.WriteLong( peer.bandwidthSequenceNum++ ); 3322 3323 unsigned int randomSize = Min( (unsigned int)(sizeof(buffer) - 12), (unsigned int)session->GetTitleStorageInt( "net_bw_test_packetSizeBytes", net_bw_test_packetSizeBytes.GetInteger() ) ); 3324 msg.WriteLong( randomSize ); 3325 3326 for ( unsigned int j=0; j < randomSize; j++ ) { 3327 msg.WriteByte( random.RandomInt( 255 ) ); 3328 } 3329 3330 unsigned int checksum = MD5_BlockChecksum( &buffer[8], randomSize ); 3331 msg.WriteLong( checksum ); 3332 3333 NET_VERBOSE_PRINT("Net: Sending bw challenge to peer %d time %d packet size %d\n", i, time, msg.GetSize() ); 3334 3335 ProcessOutgoingMsg( i, buffer, msg.GetSize(), true, OOB_BANDWIDTH_TEST ); 3336 3337 if ( session->GetTitleStorageInt( "net_bw_test_numPackets", net_bw_test_numPackets.GetInteger() ) > 0 && peer.bandwidthSequenceNum >= net_bw_test_numPackets.GetInteger() ) { 3338 int sentBytes = peers[i].packetProc->GetOutgoingBytes() - peers[i].bandwidthTestBytes; // FIXME: this won't include the last sent msg 3339 peers[i].bandwidthTestBytes = sentBytes; // this now means total bytes sent (we don't care about starting/ending total bytes sent to peer) 3340 peers[i].bandwidthChallengeSendComplete = true; 3341 3342 NET_VERBOSE_PRINT("Sent enough packets to peer %d for bandwidth test in %dms. Total bytes: %d\n", i, time - bandwidthChallengeStartTime, sentBytes ); 3343 } 3344 } 3345 3346 if ( sentAll ) { 3347 if ( bandwidthChallengeEndTime == 0 ) { 3348 // We finished sending all our packets, set the timeout time 3349 bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_host_timeout", net_bw_test_host_timeout.GetInteger() ); 3350 NET_VERBOSE_PRINT("Net: finished sending BWC to peers. Waiting until %d to hear back\n", bandwidthChallengeEndTime ); 3351 } 3352 } 3353 3354 if ( recAll ) { 3355 bandwidthChallengeFinished = true; 3356 bandwidthChallengeStartTime = 0; 3357 3358 } else if ( bandwidthChallengeEndTime != 0 && bandwidthChallengeEndTime < time ) { 3359 // Timed out waiting for someone - throttle them and move on 3360 NET_VERBOSE_PRINT("^2Net: timed out waiting for bandwidth challenge results \n"); 3361 for ( int i=0; i < peers.Num(); i++ ) { 3362 NET_VERBOSE_PRINT(" Peer[%d] %s. SentAll: %d RecAll: %d\n", i, GetPeerName(i), peers[i].bandwidthChallengeSendComplete, peers[i].bandwidthChallengeResults ); 3363 if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) { 3364 ThrottlePeerSnapRate( i ); 3365 } 3366 } 3367 bandwidthChallengeFinished = true; 3368 bandwidthChallengeStartTime = 0; 3369 } 3370 } 3371 3372 /* 3373 ======================== 3374 idLobby::UpdateBandwidthTest 3375 This will be called on clients to check current state of bandwidth testing 3376 ======================== 3377 */ 3378 void idLobby::ClientUpdateBandwidthTest() { 3379 if ( !verify( !IsHost() ) || !verify( host >= 0 ) ) { 3380 return; 3381 } 3382 3383 if ( !peers[host].IsConnected() ) { 3384 return; 3385 } 3386 3387 if ( bandwidthChallengeStartTime <= 0 ) { 3388 // Not doing a test 3389 return; 3390 } 3391 3392 int time = Sys_Milliseconds(); 3393 if ( bandwidthChallengeEndTime > time ) { 3394 // Test is still going on 3395 return; 3396 } 3397 3398 // Its been long enough since we last received bw test msg. So lets send the results to the server 3399 byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; 3400 idBitMsg msg( buffer, sizeof( buffer ) ); 3401 3402 // Send total time it took to receive all the msgs 3403 // (note, subtract net_bw_test_timeout to get 'last recevied bandwidth test packet') 3404 // (^^ Note if the last packet is fragmented and we never get it, this is technically wrong!) 3405 int totalTime = ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ) - bandwidthChallengeStartTime; 3406 msg.WriteLong( totalTime ); 3407 3408 // Send total number of complete, in order packets we got 3409 msg.WriteLong( bandwidthChallengeNumGoodSeq ); 3410 3411 // Send the overall average bandwidth in KBS 3412 // Note that sending the number of good packets is not enough. If the packets going out are fragmented, and we 3413 // drop fragments, the number of good sequences will be lower than the bandwidth we actually received. 3414 int totalIncomingBytes = peers[host].packetProc->GetIncomingBytes() - peers[host].bandwidthTestBytes; 3415 msg.WriteLong( totalIncomingBytes ); 3416 3417 idLib::Printf("^3Finished Bandwidth test: \n"); 3418 idLib::Printf(" Total time: %d\n", totalTime ); 3419 idLib::Printf(" Num good packets: %d\n", bandwidthChallengeNumGoodSeq ); 3420 idLib::Printf(" Total received byes: %d\n\n", totalIncomingBytes ); 3421 3422 bandwidthChallengeStartTime = 0; 3423 bandwidthChallengeNumGoodSeq = 0; 3424 3425 QueueReliableMessage( host, RELIABLE_BANDWIDTH_VALUES, msg.GetReadData(), msg.GetSize() ); 3426 } 3427 3428 /* 3429 ======================== 3430 idLobby::HandleBandwidhTestValue 3431 ======================== 3432 */ 3433 void idLobby::HandleBandwidhTestValue( int p, idBitMsg & msg ) { 3434 if ( !IsHost() ) { 3435 return; 3436 } 3437 3438 idLib::Printf("Received RELIABLE_BANDWIDTH_CHECK %d\n", Sys_Milliseconds() ); 3439 3440 if ( bandwidthChallengeStartTime < 0 || bandwidthChallengeFinished ) { 3441 idLib::Warning("Received bandwidth test results too early from peer %d", p ); 3442 return; 3443 } 3444 3445 int totalTime = msg.ReadLong(); 3446 int totalGoodSeq = msg.ReadLong(); 3447 int totalReceivedBytes = msg.ReadLong(); 3448 3449 // This is the % of complete packets we received. If the packets used in the BWC are big enough to fragment, then pctPackets 3450 // will be lower than bytesPct (we will have received a larger PCT of overall bandwidth than PCT of full packets received). 3451 // Im not sure if this is a useful distinction or not, but it may be good to compare against for now. 3452 float pctPackets = peers[p].bandwidthSequenceNum > 0 ? (float) totalGoodSeq / (float)peers[p].bandwidthSequenceNum : -1.0f; 3453 3454 // This is the % of total bytes sent/bytes received. 3455 float bytesPct = peers[p].bandwidthTestBytes > 0 ? (float) totalReceivedBytes / (float)peers[p].bandwidthTestBytes : -1.0f; 3456 3457 // Calculate overall bandwidth for the test. That is, total amount received over time. 3458 // We may want to expand this to also factor in an average instantaneous rate. 3459 // For now we are mostly concerned with culling out poor performing clients 3460 float peerKBS = -1.0f; 3461 if ( verify( totalTime > 0 ) ) { 3462 peerKBS = ( (float)totalReceivedBytes / 1024.0f ) / MS2SEC(totalTime); 3463 } 3464 3465 int totalSendTime = peers[p].bandwidthTestLastSendTime - peers[p].bandwidthChallengeStartSendTime; 3466 float outgoingKBS = -1.0f; 3467 if ( verify( totalSendTime > 0 ) ) { 3468 outgoingKBS = ( (float)peers[p].bandwidthTestBytes / 1024.0f ) / MS2SEC(totalSendTime); 3469 } 3470 3471 float pctKBS = peerKBS / outgoingKBS; 3472 3473 bool failedRate = ( pctKBS < session->GetTitleStorageFloat( "net_bw_test_throttle_rate_pct", net_bw_test_throttle_rate_pct.GetFloat() ) ); 3474 bool failedByte = ( bytesPct < session->GetTitleStorageFloat( "net_bw_test_throttle_byte_pct", net_bw_test_throttle_byte_pct.GetFloat() ) ); 3475 bool failedSeq = ( pctPackets < session->GetTitleStorageFloat( "net_bw_test_throttle_seq_pct", net_bw_test_throttle_seq_pct.GetFloat() ) ); 3476 3477 idLib::Printf("^3Finished Bandwidth test %s: \n", GetPeerName(p) ); 3478 idLib::Printf(" Total time: %dms\n", totalTime ); 3479 idLib::Printf(" %sNum good packets: %d (%.2f%)\n", ( failedSeq ? "^1" : "^2" ), totalGoodSeq, pctPackets ); 3480 idLib::Printf(" %sTotal received bytes: %d (%.2f%)\n", ( failedByte ? "^1" : "^2" ), totalReceivedBytes, bytesPct ); 3481 idLib::Printf(" %sEffective downstream: %.2fkbs (host: %.2fkbs) -> %.2f%\n\n", ( failedRate ? "^1" : "^2" ), peerKBS, outgoingKBS, pctKBS ); 3482 3483 // If shittConnection(totalTime, totalGoodSeq/totalSeq, totalReceivedBytes/totalSentBytes) 3484 // throttle this user: 3485 // peers[p].throttledSnapRate = baseSnapRate * 2 3486 if ( failedRate || failedByte || failedSeq ) { 3487 ThrottlePeerSnapRate( p ); 3488 } 3489 3490 3491 // See if we are finished 3492 peers[p].bandwidthChallengeResults = true; 3493 bandwidthChallengeFinished = true; 3494 for ( int i=0; i < peers.Num(); i++ ) { 3495 if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) { 3496 bandwidthChallengeFinished = false; 3497 } 3498 } 3499 3500 if ( bandwidthChallengeFinished ) { 3501 bandwidthChallengeStartTime = 0; 3502 } 3503 } 3504 3505 /* 3506 ======================== 3507 idLobby::SendPingValues 3508 Host only 3509 Periodically send all peers' pings to all peers (for the UI). 3510 ======================== 3511 */ 3512 void idLobby::SendPingValues() { 3513 if ( !verify( IsHost() ) ) { 3514 // paranoia 3515 return; 3516 } 3517 3518 const int now = Sys_Milliseconds(); 3519 3520 if ( nextSendPingValuesTime > now ) { 3521 return; 3522 } 3523 3524 nextSendPingValuesTime = now + PING_INTERVAL_MS; 3525 3526 pktPingValues_t packet; 3527 3528 memset( &packet, 0, sizeof(packet) ); 3529 3530 for( int i = 0; i < peers.Max(); ++i ) { 3531 if ( i >= peers.Num() ) { 3532 packet.pings[ i ] = -1; 3533 } else if ( peers[ i ].IsConnected() ) { 3534 packet.pings[ i ] = peers[ i ].lastPingRtt; 3535 } else { 3536 packet.pings[ i ] = -1; 3537 } 3538 } 3539 3540 byte packetCopy[ sizeof(packet) ]; 3541 idBitMsg msg( packetCopy, sizeof(packetCopy) ); 3542 for( int i = 0; i < peers.Max(); ++i ) { 3543 msg.WriteShort( packet.pings[ i ] ); 3544 } 3545 3546 for ( int i = 0; i < peers.Num(); i++ ) { 3547 if ( peers[ i ].IsConnected() ) { 3548 QueueReliableMessage( i, RELIABLE_PING_VALUES, msg.GetReadData(), msg.GetSize() ); 3549 } 3550 } 3551 } 3552 3553 /* 3554 ======================== 3555 idLobby::PumpPings 3556 Host: Periodically determine the round-trip time for a packet to all peers, and tell everyone 3557 what everyone else's ping to the host is so they can display it in the UI. 3558 Client: Indicate to the player when the server hasn't updated the ping values in too long. 3559 This is usually going to preceed a connection timeout. 3560 ======================== 3561 */ 3562 void idLobby::PumpPings() { 3563 if ( IsHost() ) { 3564 // Calculate ping to all peers 3565 PingPeers(); 3566 // Send the hosts calculated ping values to each peer to everyone has updated ping times 3567 SendPingValues(); 3568 // Do bandwidth testing 3569 ServerUpdateBandwidthTest(); 3570 // Send Migration Data 3571 SendMigrationGameData(); 3572 } else if ( IsPeer() ) { 3573 ClientUpdateBandwidthTest(); 3574 3575 if ( lastPingValuesRecvTime + PING_INTERVAL_MS + 1000 < Sys_Milliseconds() && migrationInfo.state == MIGRATE_NONE ) { 3576 for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { 3577 lobbyUser_t * user = GetLobbyUser( userIndex ); 3578 if ( !verify( user != NULL ) ) { 3579 continue; 3580 } 3581 user->pingMs = 999999; 3582 } 3583 } 3584 } 3585 } 3586 3587 /* 3588 ======================== 3589 idLobby::HandleReliablePing 3590 ======================== 3591 */ 3592 void idLobby::HandleReliablePing( int p, idBitMsg & msg ) { 3593 int c, b; 3594 msg.SaveReadState( c, b ); 3595 3596 pktPing_t ping; 3597 3598 memset( &ping, 0, sizeof( ping ) ); 3599 if ( !verify( sizeof( ping ) <= msg.GetRemainingData() ) ) { 3600 NET_VERBOSE_PRINT( "NET: Ignoring ping from peer %i because packet was the wrong size\n", p ); 3601 return; 3602 } 3603 3604 ping.timestamp = msg.ReadLong(); 3605 3606 if ( IsHost() ) { 3607 // we should probably verify here whether or not this ping was solicited or not 3608 HandlePingReply( p, ping ); 3609 } else { 3610 // this means the server is requesting a ping, so reply 3611 msg.RestoreReadState( c, b ); 3612 QueueReliableMessage( p, RELIABLE_PING, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); 3613 } 3614 } 3615 3616 /* 3617 ======================== 3618 idLobby::HandlePingReply 3619 ======================== 3620 */ 3621 void idLobby::HandlePingReply( int p, const pktPing_t & ping ) { 3622 const int now = Sys_Milliseconds(); 3623 3624 const int rtt = now - ping.timestamp; 3625 peers[p].lastPingRtt = rtt; 3626 3627 for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { 3628 lobbyUser_t * u = GetLobbyUser( userIndex ); 3629 if ( u->peerIndex == p ) { 3630 u->pingMs = rtt; 3631 } 3632 } 3633 } 3634 3635 /* 3636 ======================== 3637 idLobby::HandlePingValues 3638 ======================== 3639 */ 3640 void idLobby::HandlePingValues( idBitMsg & msg ) { 3641 pktPingValues_t packet; 3642 memset( &packet, 0, sizeof( packet ) ); 3643 for( int i = 0; i < peers.Max(); ++i ) { 3644 packet.pings[ i ] = msg.ReadShort(); 3645 } 3646 3647 assert( IsPeer() ); 3648 3649 lastPingValuesRecvTime = Sys_Milliseconds(); 3650 3651 for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) { 3652 lobbyUser_t * u = GetLobbyUser( userIndex ); 3653 if ( u->peerIndex != -1 && verify( u->peerIndex >= 0 && u->peerIndex < MAX_PEERS ) ) { 3654 u->pingMs = packet.pings[ u->peerIndex ]; 3655 } else { 3656 u->pingMs = 0; 3657 } 3658 } 3659 3660 // Stuff our ping in the hosts slot 3661 if ( peerIndexOnHost != -1 && verify( peerIndexOnHost >= 0 && peerIndexOnHost < MAX_PEERS ) ) { 3662 peers[host].lastPingRtt = packet.pings[ peerIndexOnHost ]; 3663 } else { 3664 peers[host].lastPingRtt = 0; 3665 } 3666 } 3667 3668 /* 3669 ======================== 3670 idLobby::SendAnotherFragment 3671 Other than connectionless sends, this should be the chokepoint for sending packets to peers. 3672 ======================== 3673 */ 3674 bool idLobby::SendAnotherFragment( int p ) { 3675 peer_t & peer = peers[p]; 3676 3677 if ( !peer.IsConnected() ) { // Not connected to any mode (party or game), so no need to send 3678 return false; 3679 } 3680 3681 if ( !peer.packetProc->HasMoreFragments() ) { 3682 return false; // No fragments to send for this peer 3683 } 3684 3685 if ( !CanSendMoreData( p ) ) { 3686 return false; // We need to throttle the sends so we don't saturate the connection 3687 } 3688 3689 int time = Sys_Milliseconds(); 3690 3691 if ( time - peer.lastFragmentSendTime < 2 ) { 3692 NET_VERBOSE_PRINT("Too soon to send another packet. Delta: %d \n", ( time-peer.lastFragmentSendTime) ); 3693 return false; // Too soon to send another fragment 3694 } 3695 3696 peer.lastFragmentSendTime = time; 3697 3698 bool sentFragment = false; 3699 3700 while ( true ) { 3701 idBitMsg msg; 3702 // We use the final packet size here because it has been processed, and no more headers will be added 3703 byte buffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ]; 3704 msg.InitWrite( buffer, sizeof( buffer ) ); 3705 3706 if ( !peers[p].packetProc->GetSendFragment( time, peers[p].sessionID, msg ) ) { 3707 break; 3708 } 3709 3710 const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE ); 3711 3712 msg.BeginReading(); 3713 sessionCB->SendRawPacket( peers[p].address, msg.GetReadData(), msg.GetSize(), useDirectPort ); 3714 sentFragment = true; 3715 break; // Comment this out to send all fragments in one burst 3716 } 3717 3718 if ( peer.packetProc->HasMoreFragments() ) { 3719 NET_VERBOSE_PRINT("More packets left after ::SendAnotherFragment\n"); 3720 } 3721 3722 return sentFragment; 3723 } 3724 3725 /* 3726 ======================== 3727 idLobby::CanSendMoreData 3728 ======================== 3729 */ 3730 bool idLobby::CanSendMoreData( int p ) { 3731 if ( !verify( p >= 0 && p < peers.Num() ) ) { 3732 NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not a peer\n", p ); 3733 return false; 3734 } 3735 peer_t & peer = peers[p]; 3736 if ( !peer.IsConnected() ) { 3737 NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not connected\n", p ); 3738 return false; 3739 } 3740 3741 return peer.packetProc->CanSendMoreData(); 3742 } 3743 3744 /* 3745 ======================== 3746 idLobby::ProcessOutgoingMsg 3747 ======================== 3748 */ 3749 void idLobby::ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ) { 3750 3751 peer_t & peer = peers[p]; 3752 3753 if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) { 3754 idLib::Printf( "peer.GetConnectionState() != CONNECTION_ESTABLISHED\n" ); 3755 return; // Peer not fully connected for this session type, return 3756 } 3757 3758 if ( peer.packetProc->HasMoreFragments() ) { 3759 idLib::Error( "FATAL: Attempt to process a packet while fragments still need to be sent.\n" ); // We can't handle this case 3760 } 3761 3762 int currentTime = Sys_Milliseconds(); 3763 3764 // if ( currentTime - peer.lastProcTime < 30 ) { 3765 // idLib::Printf("ProcessOutgoingMsg called within %dms %s\n", (currentTime - peer.lastProcTime), GetLobbyName() ); 3766 // } 3767 3768 peer.lastProcTime = currentTime; 3769 3770 if ( !isOOB ) { 3771 // Keep track of the last time an in-band packet was sent 3772 // (used for things like knowing when reliables could have been last sent) 3773 peer.lastInBandProcTime = peer.lastProcTime; 3774 } 3775 3776 idBitMsg msg; 3777 msg.InitRead( (byte*)data, size ); 3778 peer.packetProc->ProcessOutgoing( currentTime, msg, isOOB, userData ); 3779 } 3780 3781 /* 3782 ======================== 3783 idLobby::ResendReliables 3784 ======================== 3785 */ 3786 void idLobby::ResendReliables( int p ) { 3787 3788 peer_t & peer = peers[p]; 3789 3790 if ( !peer.IsConnected() ) { 3791 return; 3792 } 3793 3794 if ( peer.packetProc->HasMoreFragments() ) { 3795 return; // We can't send more data while fragments are still being sent out 3796 } 3797 3798 if ( !CanSendMoreData( p ) ) { 3799 return; 3800 } 3801 3802 int time = Sys_Milliseconds(); 3803 3804 const int DEFAULT_MIN_RESEND = 20; // Quicker resend while not in game to speed up resource transmission acks 3805 const int DEFAULT_MIN_RESEND_INGAME = 100; 3806 3807 int resendWait = DEFAULT_MIN_RESEND_INGAME; 3808 3809 if ( sessionCB->GetState() == idSession::INGAME ) { 3810 // setup some minimum waits and account for ping 3811 resendWait = Max( DEFAULT_MIN_RESEND_INGAME, peer.lastPingRtt / 2 ); 3812 if ( lobbyType == TYPE_PARTY ) { 3813 resendWait = Max( 500, resendWait ); // party session does not need fast frequency at all once in game 3814 } 3815 } else { 3816 // don't trust the ping when still loading stuff 3817 // need to resend fast to speed up transmission of network decls 3818 resendWait = DEFAULT_MIN_RESEND; 3819 } 3820 3821 if ( time - peer.lastInBandProcTime < resendWait ) { 3822 // no need to resend reliables if they went out on an in-band packet recently 3823 return; 3824 } 3825 3826 if ( peer.packetProc->NumQueuedReliables() > 0 || peer.packetProc->NeedToSendReliableAck() ) { 3827 //NET_VERBOSE_PRINT( "NET: ResendReliables %s\n", GetLobbyName() ); 3828 ProcessOutgoingMsg( p, NULL, 0, false, 0 ); // Force an empty unreliable msg so any reliables will get processed as well 3829 } 3830 } 3831 3832 /* 3833 ======================== 3834 idLobby::PumpPackets 3835 ======================== 3836 */ 3837 void idLobby::PumpPackets() { 3838 int newTime = Sys_Milliseconds(); 3839 3840 for ( int p = 0; p < peers.Num(); p++ ) { 3841 if ( peers[p].IsConnected() ) { 3842 peers[p].packetProc->RefreshRates( newTime ); 3843 } 3844 } 3845 3846 // Resend reliable msg's (do this before we send out the fragments) 3847 for ( int p = 0; p < peers.Num(); p++ ) { 3848 ResendReliables( p ); 3849 } 3850 3851 // 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 3852 // NOTE - We used to only send these to the host, but the host needs to also send these to clients 3853 for ( int p = 0; p < peers.Num(); p++ ) { 3854 if ( !peers[p].IsConnected() || peers[p].packetProc->HasMoreFragments() ) { 3855 continue; 3856 } 3857 if ( newTime - peers[p].lastProcTime > 1000 * PEER_HEARTBEAT_IN_SECONDS ) { 3858 //NET_VERBOSE_PRINT( "NET: ProcessOutgoing Heartbeat %s\n", GetLobbyName() ); 3859 ProcessOutgoingMsg( p, NULL, 0, false, 0 ); 3860 } 3861 } 3862 3863 // Send any unsent fragments for each peer (do this last) 3864 for ( int p = 0; p < peers.Num(); p++ ) { 3865 SendAnotherFragment( p ); 3866 } 3867 } 3868 3869 /* 3870 ======================== 3871 idLobby::UpdateMatchParms 3872 ======================== 3873 */ 3874 void idLobby::UpdateMatchParms( const idMatchParameters & p ) { 3875 if ( !IsHost() ) { 3876 return; 3877 } 3878 3879 parms = p; 3880 3881 // Update lobbyBackend with parms 3882 if ( lobbyBackend != NULL ) { 3883 lobbyBackend->UpdateMatchParms( parms ); 3884 } 3885 3886 SendMatchParmsToPeers(); 3887 } 3888 3889 /* 3890 ======================== 3891 idLobby::GetHostUserName 3892 ======================== 3893 */ 3894 const char * idLobby::GetHostUserName() const { 3895 if ( !IsLobbyActive() ) { 3896 return INVALID_LOBBY_USER_NAME; 3897 } 3898 return GetPeerName( -1 ); // This will just grab the first user with this peerIndex (which should be the host) 3899 } 3900 3901 /* 3902 ======================== 3903 idLobby::SendReliable 3904 ======================== 3905 */ 3906 void idLobby::SendReliable( int type, idBitMsg & msg, bool callReceiveReliable /*= true*/, peerMask_t sessionUserMask /*= MAX_UNSIGNED_TYPE( peerMask_t ) */ ) { 3907 //assert( lobbyType == GetActingGameStateLobbyType() ); 3908 3909 assert( type < 256 ); // QueueReliable only accepts a byte for message type 3910 3911 // the queuing below sends the whole message 3912 // 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 3913 assert( msg.GetReadCount() + msg.GetReadBit() == 0 ); 3914 3915 if ( callReceiveReliable ) { 3916 // NOTE: this will put the msg's read status to fully read - which is why the assert check is above 3917 common->NetReceiveReliable( -1, type, msg ); 3918 } 3919 3920 uint32 sentPeerMask = 0; 3921 for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { 3922 lobbyUser_t * user = GetLobbyUser( i ); 3923 if ( user->peerIndex == -1 ) { 3924 continue; 3925 } 3926 3927 // We only care about sending these to peers in our party lobby 3928 if ( user->IsDisconnected() ) { 3929 continue; 3930 } 3931 3932 // Don't sent to a user if they are in the exlusion session user mask 3933 if ( sessionUserMask != 0 && ( sessionUserMask & ( BIT( i ) ) ) == 0 ) { 3934 continue; 3935 } 3936 3937 const int peerIndex = user->peerIndex; 3938 3939 if ( peerIndex >= peers.Num() ) { 3940 continue; 3941 } 3942 3943 peer_t & peer = peers[peerIndex]; 3944 3945 if ( !peer.IsConnected() ) { 3946 continue; 3947 } 3948 3949 if ( ( sentPeerMask & ( 1 << user->peerIndex ) ) == 0 ) { 3950 QueueReliableMessage( user->peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData(), msg.GetSize() ); 3951 sentPeerMask |= 1 << user->peerIndex; 3952 } 3953 } 3954 } 3955 3956 /* 3957 ======================== 3958 idLobby::SendReliableToLobbyUser 3959 can only be used on the server. will take care of calling locally if addressed to player 0 3960 ======================== 3961 */ 3962 void idLobby::SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) { 3963 assert( lobbyType == GetActingGameStateLobbyType() ); 3964 assert( type < 256 ); // QueueReliable only accepts a byte for message type 3965 assert( IsHost() ); // This function should only be called in the server atm 3966 const int peerIndex = PeerIndexFromLobbyUser( lobbyUserID ); 3967 if ( peerIndex >= 0 ) { 3968 // will send the remainder of a message that was started reading through, but not handling a partial byte read 3969 assert( msg.GetReadBit() == 0 ); 3970 QueueReliableMessage( peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); 3971 } else { 3972 common->NetReceiveReliable( -1, type, msg ); 3973 } 3974 } 3975 3976 /* 3977 ======================== 3978 idLobby::SendReliableToHost 3979 will make sure to invoke locally if used on the server 3980 ======================== 3981 */ 3982 void idLobby::SendReliableToHost( int type, idBitMsg & msg ) { 3983 assert( lobbyType == GetActingGameStateLobbyType() ); 3984 3985 if ( IsHost() ) { 3986 common->NetReceiveReliable( -1, type, msg ); 3987 } else { 3988 // will send the remainder of a message that was started reading through, but not handling a partial byte read 3989 assert( msg.GetReadBit() == 0 ); 3990 QueueReliableMessage( host, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); 3991 } 3992 } 3993 3994 /* 3995 ================================================================================================ 3996 idLobby::reliablePlayerToPlayerHeader_t 3997 ================================================================================================ 3998 */ 3999 4000 /* 4001 ======================== 4002 idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t 4003 ======================== 4004 */ 4005 idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t() : fromSessionUserIndex( -1 ), toSessionUserIndex( -1 ) { 4006 } 4007 4008 /* 4009 ======================== 4010 idSessionLocal::reliablePlayerToPlayerHeader_t::Read 4011 ======================== 4012 */ 4013 bool idLobby::reliablePlayerToPlayerHeader_t::Read( idLobby * lobby, idBitMsg & msg ) { 4014 assert( lobby != NULL ); 4015 4016 lobbyUserID_t lobbyUserIDFrom; 4017 lobbyUserID_t lobbyUserIDTo; 4018 4019 lobbyUserIDFrom.ReadFromMsg( msg ); 4020 lobbyUserIDTo.ReadFromMsg( msg ); 4021 4022 fromSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDFrom ); 4023 toSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDTo ); 4024 4025 if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) { 4026 return false; 4027 } 4028 4029 if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) { 4030 return false; 4031 } 4032 4033 return true; 4034 } 4035 4036 /* 4037 ======================== 4038 idLobby::reliablePlayerToPlayerHeader_t::Write 4039 ======================== 4040 */ 4041 bool idLobby::reliablePlayerToPlayerHeader_t::Write( idLobby * lobby, idBitMsg & msg ) { 4042 4043 4044 if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) { 4045 return false; 4046 } 4047 4048 if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) { 4049 return false; 4050 } 4051 4052 lobby->GetLobbyUser( fromSessionUserIndex )->lobbyUserID.WriteToMsg( msg ); 4053 lobby->GetLobbyUser( toSessionUserIndex )->lobbyUserID.WriteToMsg( msg ); 4054 4055 return true; 4056 } 4057 4058 /* 4059 ======================== 4060 idLobby::GetNumActiveLobbyUsers 4061 ======================== 4062 */ 4063 int idLobby::GetNumActiveLobbyUsers() const { 4064 int numActive = 0; 4065 for ( int i = 0; i < GetNumLobbyUsers(); ++i ) { 4066 if ( !GetLobbyUser( i )->IsDisconnected() ) { 4067 numActive++; 4068 } 4069 } 4070 return numActive; 4071 } 4072 4073 /* 4074 ======================== 4075 idLobby::AllPeersInGame 4076 ======================== 4077 */ 4078 bool idLobby::AllPeersInGame() const { 4079 assert( lobbyType == GetActingGameStateLobbyType() ); // This function doesn't make sense on a party lobby currently 4080 4081 for ( int p = 0; p < peers.Num(); p++ ) { 4082 if ( peers[p].IsConnected() && !peers[p].inGame ) { 4083 return false; 4084 } 4085 } 4086 4087 return true; 4088 } 4089 4090 /* 4091 ======================== 4092 idLobby::PeerIndexFromLobbyUser 4093 ======================== 4094 */ 4095 int idLobby::PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const { 4096 const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID ); 4097 4098 const lobbyUser_t * user = GetLobbyUser( lobbyUserIndex ); 4099 4100 if ( user == NULL ) { 4101 // This needs to be OK for bot support ( or else add bots at the session level ) 4102 return -1; 4103 } 4104 4105 return user->peerIndex; 4106 } 4107 4108 /* 4109 ======================== 4110 idLobby::GetPeerTimeSinceLastPacket 4111 ======================== 4112 */ 4113 int idLobby::GetPeerTimeSinceLastPacket( int peerIndex ) const { 4114 if ( peerIndex < 0 ) { 4115 return 0; 4116 } 4117 return Sys_Milliseconds() - peers[peerIndex].lastHeartBeat; 4118 } 4119 4120 /* 4121 ======================== 4122 idLobby::GetActingGameStateLobbyType 4123 ======================== 4124 */ 4125 idLobby::lobbyType_t idLobby::GetActingGameStateLobbyType() const { 4126 extern idCVar net_useGameStateLobby; 4127 return ( net_useGameStateLobby.GetBool() ) ? TYPE_GAME_STATE : TYPE_GAME; 4128 } 4129 4130 //======================================================================================================================== 4131 // idLobby::peer_t 4132 //======================================================================================================================== 4133 4134 /* 4135 ======================== 4136 idLobby::peer_t::GetConnectionState 4137 ======================== 4138 */ 4139 idLobby::connectionState_t idLobby::peer_t::GetConnectionState() const { 4140 return connectionState; 4141 }