sys_session_local.cpp (130853B)
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_session_local.h" 31 #include "sys_voicechat.h" 32 #include "sys_dedicated_server_search.h" 33 34 35 idCVar ui_skinIndex( "ui_skinIndex", "0", CVAR_ARCHIVE, "Selected skin index" ); 36 idCVar ui_autoSwitch( "ui_autoSwitch", "1", CVAR_ARCHIVE | CVAR_BOOL, "auto switch weapon" ); 37 idCVar ui_autoReload( "ui_autoReload", "1", CVAR_ARCHIVE | CVAR_BOOL, "auto reload weapon" ); 38 39 idCVar net_maxSearchResults( "net_maxSearchResults", "25", CVAR_INTEGER, "Max results that are allowed to be returned in a search request" ); 40 idCVar net_maxSearchResultsToTry( "net_maxSearchResultsToTry", "5", CVAR_INTEGER, "Max results to try before giving up." ); // At 15 second timeouts per, 1 min 15 worth of connecting attempt time 41 42 idCVar net_LobbyCoalesceTimeInSeconds( "net_LobbyCoalesceTimeInSeconds", "30", CVAR_INTEGER, "Time in seconds when a lobby will try to coalesce with another lobby when there is only one user." ); 43 idCVar net_LobbyRandomCoalesceTimeInSeconds( "net_LobbyRandomCoalesceTimeInSeconds", "3", CVAR_INTEGER, "Random time to add to net_LobbyCoalesceTimeInSeconds" ); 44 45 idCVar net_useGameStateLobby( "net_useGameStateLobby", "0", CVAR_BOOL, "" ); 46 //idCVar net_useGameStateLobby( "net_useGameStateLobby", "1", CVAR_BOOL, "" ); 47 48 #if !defined( ID_RETAIL ) || defined( ID_RETAIL_INTERNAL ) 49 idCVar net_ignoreTitleStorage( "net_ignoreTitleStorage", "0", CVAR_BOOL, "Ignore title storage" ); 50 #endif 51 52 idCVar net_maxLoadResourcesTimeInSeconds( "net_maxLoadResourcesTimeInSeconds", "0", CVAR_INTEGER, "How long, in seconds, clients have to load resources. Used for loose asset builds." ); 53 idCVar net_migrateHost( "net_migrateHost", "-1", CVAR_INTEGER, "Become host of session (0 = party, 1 = game) for testing purposes" ); 54 extern idCVar net_debugBaseStates; 55 56 idCVar net_testPartyMemberConnectFail( "net_testPartyMemberConnectFail", "-1", CVAR_INTEGER, "Force this party member index to fail to connect to games." ); 57 58 //FIXME: this could use a better name. 59 idCVar net_offlineTransitionThreshold( "net_offlineTransitionThreshold", "1000", CVAR_INTEGER, "Time, in milliseconds, to wait before kicking back to the main menu when a profile losses backend connection during an online game"); 60 61 idCVar net_port( "net_port", "27015", CVAR_INTEGER, "host port number" ); // Port to host when using dedicated servers, port to broadcast on when looking for a dedicated server to connect to 62 idCVar net_headlessServer( "net_headlessServer", "0", CVAR_BOOL, "toggle to automatically host a game and allow peer[0] to control menus" ); 63 64 const char * idSessionLocal::stateToString[ NUM_STATES ] = { 65 ASSERT_ENUM_STRING( STATE_PRESS_START, 0 ), 66 ASSERT_ENUM_STRING( STATE_IDLE, 1 ), 67 ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_HOST, 2 ), 68 ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_PEER, 3 ), 69 ASSERT_ENUM_STRING( STATE_GAME_LOBBY_HOST, 4 ), 70 ASSERT_ENUM_STRING( STATE_GAME_LOBBY_PEER, 5 ), 71 ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_HOST, 6 ), 72 ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_PEER, 7 ), 73 ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, 8 ), 74 ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, 9 ), 75 ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, 10 ), 76 ASSERT_ENUM_STRING( STATE_FIND_OR_CREATE_MATCH, 11 ), 77 ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_PARTY, 12 ), 78 ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME, 13 ), 79 ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME_STATE, 14 ), 80 ASSERT_ENUM_STRING( STATE_BUSY, 15 ), 81 ASSERT_ENUM_STRING( STATE_LOADING, 16 ), 82 ASSERT_ENUM_STRING( STATE_INGAME, 17 ), 83 }; 84 85 struct netVersion_s { 86 netVersion_s() { sprintf( string, "%s.%d", ENGINE_VERSION, BUILD_NUMBER ); } 87 char string[256]; 88 } netVersion; 89 90 /* 91 ======================== 92 NetGetVersionChecksum 93 ======================== 94 */ 95 unsigned long NetGetVersionChecksum() { 96 #if 0 97 return idStr( com_version.GetString() ).Hash(); 98 #else 99 unsigned long ret = 0; 100 101 CRC32_InitChecksum( ret ); 102 CRC32_UpdateChecksum( ret, netVersion.string, idStr::Length( netVersion.string ) ); 103 CRC32_FinishChecksum( ret ); 104 105 NET_VERBOSE_PRINT( "NetGetVersionChecksum - string : %s\n", netVersion.string ); 106 NET_VERBOSE_PRINT( "NetGetVersionChecksum - checksum : %i\n", ret ); 107 return ret; 108 #endif 109 } 110 111 /* 112 ======================== 113 idSessionLocal::idSessionLocal 114 ======================== 115 */ 116 idSessionLocal::idSessionLocal() : 117 processorSaveFiles( new (TAG_SAVEGAMES) idSaveGameProcessorSaveFiles ), 118 processorLoadFiles( new (TAG_SAVEGAMES) idSaveGameProcessorLoadFiles ), 119 processorDelete( new (TAG_SAVEGAMES) idSaveGameProcessorDelete ), 120 processorEnumerate( new (TAG_SAVEGAMES) idSaveGameProcessorEnumerateGames ) { 121 InitBaseState(); 122 } 123 124 /* 125 ======================== 126 idSessionLocal::idSessionLocal 127 ======================== 128 */ 129 idSessionLocal::~idSessionLocal() { 130 delete processorSaveFiles; 131 delete processorLoadFiles; 132 delete processorDelete; 133 delete processorEnumerate; 134 delete sessionCallbacks; 135 } 136 137 138 /* 139 ======================== 140 idSessionLocal::InitBaseState 141 ======================== 142 */ 143 void idSessionLocal::InitBaseState() { 144 145 //assert( mem.IsGlobalHeap() ); 146 147 localState = STATE_PRESS_START; 148 sessionOptions = 0; 149 currentID = 0; 150 151 sessionCallbacks = new (TAG_NETWORKING) idSessionLocalCallbacks( this ); 152 153 connectType = CONNECT_NONE; 154 connectTime = 0; 155 156 upstreamDropRate = 0.0f; 157 upstreamDropRateTime = 0; 158 upstreamQueueRate = 0.0f; 159 upstreamQueueRateTime = 0; 160 queuedBytes = 0; 161 162 lastVoiceSendtime = 0; 163 hasShownVoiceRestrictionDialog = false; 164 165 isSysUIShowing = false; 166 167 pendingInviteDevice = 0; 168 pendingInviteMode = PENDING_INVITE_NONE; 169 170 downloadedContent.Clear(); 171 marketplaceHasNewContent = false; 172 173 offlineTransitionTimerStart = 0; 174 showMigratingInfoStartTime = 0; 175 nextGameCoalesceTime = 0; 176 gameLobbyWasCoalesced = false; 177 numFullSnapsReceived = 0; 178 179 flushedStats = false; 180 181 titleStorageLoaded = false; 182 183 droppedByHost = false; 184 loadingID = 0; 185 186 storedPeer = -1; 187 storedMsgType = -1; 188 189 inviteInfoRequested = false; 190 191 enumerationHandle = 0; 192 193 waitingOnGameStateMembersToLeaveTime = 0; 194 waitingOnGameStateMembersToJoinTime = 0; 195 } 196 197 /* 198 ======================== 199 idSessionLocal::FinishDisconnect 200 ======================== 201 */ 202 void idSessionLocal::FinishDisconnect() { 203 GetPort().Close(); 204 while ( sendQueue.Peek() != NULL ) { 205 sendQueue.RemoveFirst(); 206 } 207 while ( recvQueue.Peek() != NULL ) { 208 recvQueue.RemoveFirst(); 209 } 210 } 211 212 //==================================================================================== 213 214 idCVar net_connectTimeoutInSeconds( "net_connectTimeoutInSeconds", "15", CVAR_INTEGER, "timeout (in seconds) while connecting" ); 215 216 /* 217 ======================== 218 idSessionLocal::CreatePartyLobby 219 ======================== 220 */ 221 void idSessionLocal::CreatePartyLobby( const idMatchParameters & parms_ ) { 222 NET_VERBOSE_PRINT( "NET: CreatePartyLobby\n" ); 223 224 // Shutdown any possible party lobby 225 GetPartyLobby().Shutdown(); 226 GetPartyLobby().ResetAllMigrationState(); 227 228 // Shutdown any possible game lobby 229 GetGameLobby().Shutdown(); 230 GetGameStateLobby().Shutdown(); 231 232 // Start hosting a new party lobby 233 GetPartyLobby().StartHosting( parms_ ); 234 235 connectType = CONNECT_NONE; 236 connectTime = Sys_Milliseconds(); 237 238 // Wait for it to complete 239 SetState( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY ); 240 } 241 242 /* 243 ======================== 244 idSessionLocal::CreateMatch 245 ======================== 246 */ 247 void idSessionLocal::CreateMatch( const idMatchParameters & p ) { 248 NET_VERBOSE_PRINT( "NET: CreateMatch\n" ); 249 250 if ( ( p.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) && !GetPartyLobby().IsLobbyActive() ) { 251 NET_VERBOSE_PRINT( "NET: CreateMatch MATCH_PARTY_INVITE_PLACEHOLDER\n" ); 252 CreatePartyLobby( p ); 253 connectType = CONNECT_NONE; 254 return; 255 } 256 257 // Shutdown any possible game lobby 258 GetGameLobby().Shutdown(); 259 GetGameStateLobby().Shutdown(); 260 GetGameLobby().ResetAllMigrationState(); 261 262 // Start hosting a new game lobby 263 GetGameLobby().StartHosting( p ); 264 265 connectType = CONNECT_NONE; 266 connectTime = Sys_Milliseconds(); 267 268 // Wait for it to complete 269 SetState( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY ); 270 } 271 272 /* 273 ======================== 274 idSessionLocal::CreateGameStateLobby 275 ======================== 276 */ 277 void idSessionLocal::CreateGameStateLobby( const idMatchParameters & p ) { 278 NET_VERBOSE_PRINT( "NET: CreateGameStateLobby\n" ); 279 280 // Shutdown any possible game state lobby 281 GetGameStateLobby().Shutdown(); 282 GetGameStateLobby().ResetAllMigrationState(); 283 284 // Start hosting a new game lobby 285 GetGameStateLobby().StartHosting( p ); 286 287 connectType = CONNECT_NONE; 288 connectTime = Sys_Milliseconds(); 289 290 waitingOnGameStateMembersToLeaveTime = 0; // Make sure to reset 291 waitingOnGameStateMembersToJoinTime = 0; 292 293 // Wait for it to complete 294 SetState( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY ); 295 } 296 297 /* 298 ======================== 299 idSessionLocal::FindOrCreateMatch 300 ======================== 301 */ 302 void idSessionLocal::FindOrCreateMatch( const idMatchParameters & p ) { 303 NET_VERBOSE_PRINT( "NET: FindOrCreateMatch\n" ); 304 305 if ( ( p.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) && !GetPartyLobby().IsLobbyActive() ) { 306 NET_VERBOSE_PRINT( "NET: FindOrCreateMatch MATCH_PARTY_INVITE_PLACEHOLDER\n" ); 307 CreatePartyLobby( p ); 308 connectType = CONNECT_FIND_OR_CREATE; 309 return; 310 } 311 312 // Shutdown any possible game lobby 313 GetGameLobby().Shutdown(); 314 GetGameStateLobby().Shutdown(); 315 GetGameLobby().ResetAllMigrationState(); 316 317 // Start searching for a game 318 GetGameLobby().StartFinding( p ); 319 320 connectType = CONNECT_FIND_OR_CREATE; 321 connectTime = Sys_Milliseconds(); 322 gameLobbyWasCoalesced = false; 323 numFullSnapsReceived = 0; 324 325 // Wait for searching to complete 326 SetState( STATE_FIND_OR_CREATE_MATCH ); 327 } 328 329 /* 330 ======================== 331 idSessionLocal::StartLoading 332 ======================== 333 */ 334 void idSessionLocal::StartLoading() { 335 NET_VERBOSE_PRINT( "NET: StartLoading\n" ); 336 337 if ( MatchTypeIsOnline( GetActingGameStateLobby().parms.matchFlags ) ) { 338 if ( !GetActingGameStateLobby().IsHost() ) { 339 idLib::Warning( "Ignoring call to StartLoading because we are not the host. state is %s", stateToString[ localState ] ); 340 return; 341 } 342 343 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 344 if ( GetActingGameStateLobby().peers[p].IsConnected() ) { 345 GetActingGameStateLobby().QueueReliableMessage( p, idLobby::RELIABLE_START_LOADING ); 346 GetActingGameStateLobby().peers[p].startResourceLoadTime = Sys_Milliseconds(); 347 } 348 } 349 } 350 351 VerifySnapshotInitialState(); 352 353 SetState( STATE_LOADING ); 354 } 355 356 /* 357 ======================== 358 idSessionLocal::StartMatch 359 ======================== 360 */ 361 void idSessionLocal::StartMatch() { 362 NET_VERBOSE_PRINT( "NET: StartMatch\n" ); 363 364 if ( net_headlessServer.GetBool() ) { 365 StartLoading(); // This is so we can force start matches on headless servers to test performance using bots 366 return; 367 } 368 369 if ( localState != STATE_GAME_LOBBY_HOST ) { 370 idLib::Warning( "idSessionLocal::StartMatch called when not hosting game lobby" ); 371 return; 372 } 373 374 assert( !GetGameStateLobby().IsLobbyActive() ); 375 376 // make absolutely sure we only call StartMatch once per migrate 377 GetGameLobby().migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = true; 378 379 // Clear snap ack queue between games 380 GetGameLobby().snapDeltaAckQueue.Clear(); 381 382 extern idCVar net_bw_challenge_enable; 383 if ( session->GetTitleStorageBool( "net_bw_challenge_enable", net_bw_challenge_enable.GetBool() ) && GetGameLobby().HasActivePeers() ) { 384 GetGameLobby().bandwidthChallengeFinished = false; 385 StartOrContinueBandwidthChallenge( false ); 386 } 387 388 if ( GetGameLobby().BandwidthTestStarted() ) { 389 // Put session in busy state 390 NET_VERBOSE_PRINT( "NET: StartMatch -> Start Bandwidth Challenge\n" ); 391 SetState( STATE_BUSY ); 392 } else { 393 // Start loading 394 StartLoading(); 395 } 396 } 397 398 /* 399 ======================== 400 idSessionLocal::GetBackState 401 ======================== 402 */ 403 idSessionLocal::sessionState_t idSessionLocal::GetBackState() { 404 sessionState_t currentState = GetState(); 405 406 const bool isInGameLobby = currentState == GAME_LOBBY; 407 const bool isInPartyLobby = currentState == PARTY_LOBBY; 408 const bool isInGame = currentState == INGAME || currentState == LOADING; // Counting loading as ingame as far as what back state to go to 409 410 if ( isInGame ) { 411 return GAME_LOBBY; // If in the game, go back to game lobby 412 } 413 414 if ( !isInPartyLobby && isInGameLobby && ShouldHavePartyLobby() ) { 415 return PARTY_LOBBY; // If in the game lobby, and we should have a party lobby, and we are the host, go back to party lobby 416 } 417 418 if ( currentState != IDLE ) { 419 return IDLE; // From here, go to idle if we aren't there yet 420 } 421 422 return PRESS_START; // Otherwise, go back to press start 423 } 424 425 /* 426 ======================== 427 idSessionLocal::Cancel 428 ======================== 429 */ 430 void idSessionLocal::Cancel() { 431 NET_VERBOSE_PRINT( "NET: Cancel\n" ); 432 433 if ( localState == STATE_PRESS_START ) { 434 return; // We're as far back as we can go 435 } 436 437 ClearVoiceGroups(); // this is here as a catch-all 438 439 // See what state we need to go to 440 switch ( GetBackState() ) { 441 case GAME_LOBBY: 442 EndMatch(); // End current match to go to game lobby 443 break; 444 445 case PARTY_LOBBY: 446 if ( GetPartyLobby().IsHost() ) { 447 if ( sessionOptions & OPTION_LEAVE_WITH_PARTY ) { 448 // NOTE - This will send a message on the team lobby channel, 449 // so it won't be affected by the fact that we're shutting down the game lobby 450 GetPartyLobby().NotifyPartyOfLeavingGameLobby(); 451 } else { 452 // Host wants to be alone, disconnect all peers from the party 453 GetPartyLobby().DisconnectAllPeers(); 454 } 455 456 // End the game lobby, and go back to party lobby as host 457 GetGameLobby().Shutdown(); 458 GetGameStateLobby().Shutdown(); 459 SetState( STATE_PARTY_LOBBY_HOST ); 460 461 // Always remove this flag. SendGoodbye uses this to determine if we should send a "leave with party" 462 // and we don't want this flag hanging around, and causing false positives when it's called in the future. 463 // Make them set this each time. 464 sessionOptions &= ~OPTION_LEAVE_WITH_PARTY; 465 } else { 466 // If we aren't the host of a party and we want to go back to one, we need to create a party now 467 CreatePartyLobby( GetPartyLobby().parms ); 468 } 469 break; 470 471 case IDLE: 472 // Go back to main menu 473 GetGameLobby().Shutdown(); 474 GetGameStateLobby().Shutdown(); 475 GetPartyLobby().Shutdown(); 476 SetState( STATE_IDLE ); 477 break; 478 479 case PRESS_START: 480 // Go back to press start/main 481 GetGameLobby().Shutdown(); 482 GetGameStateLobby().Shutdown(); 483 GetPartyLobby().Shutdown(); 484 SetState( STATE_PRESS_START ); 485 break; 486 } 487 488 // Validate the current lobby immediately 489 ValidateLobbies(); 490 } 491 492 /* 493 ======================== 494 idSessionLocal::MoveToPressStart 495 ======================== 496 */ 497 void idSessionLocal::MoveToPressStart() { 498 if ( localState != STATE_PRESS_START ) { 499 assert( signInManager != NULL ); 500 signInManager->RemoveAllLocalUsers(); 501 hasShownVoiceRestrictionDialog = false; 502 MoveToMainMenu(); 503 session->FinishDisconnect(); 504 SetState( STATE_PRESS_START ); 505 } 506 } 507 508 /* 509 ======================== 510 idSessionLocal::ShouldShowMigratingDialog 511 ======================== 512 */ 513 bool idSessionLocal::ShouldShowMigratingDialog() const { 514 const idLobby * activeLobby = GetActivePlatformLobby(); 515 516 if ( activeLobby == NULL ) { 517 return false; 518 } 519 520 return activeLobby->ShouldShowMigratingDialog(); 521 } 522 523 /* 524 ======================== 525 idSessionLocal::IsCurrentLobbyMigrating 526 ======================== 527 */ 528 bool idSessionLocal::IsCurrentLobbyMigrating() const { 529 const idLobby * activeLobby = GetActivePlatformLobby(); 530 531 if ( activeLobby == NULL ) { 532 return false; 533 } 534 535 return activeLobby->IsMigrating(); 536 } 537 538 /* 539 ======================== 540 idSessionLocal::IsLosingConnectionToHost 541 ======================== 542 */ 543 bool idSessionLocal::IsLosingConnectionToHost() const { 544 return GetActingGameStateLobby().IsLosingConnectionToHost(); 545 } 546 547 /* 548 ======================== 549 idSessionLocal::WasMigrationGame 550 returns true if we are hosting a migrated game and we had valid migration data 551 ======================== 552 */ 553 bool idSessionLocal::WasMigrationGame() const { 554 return GetGameLobby().IsMigratedStatsGame(); 555 } 556 557 /* 558 ======================== 559 idSessionLocal::ShouldRelaunchMigrationGame 560 returns true if we are hosting a migrated game and we had valid migration data 561 ======================== 562 */ 563 bool idSessionLocal::ShouldRelaunchMigrationGame() const { 564 return GetGameLobby().ShouldRelaunchMigrationGame() && !IsCurrentLobbyMigrating(); 565 } 566 567 /* 568 ======================== 569 idSessionLocal::GetMigrationGameData 570 ======================== 571 */ 572 bool idSessionLocal::GetMigrationGameData( idBitMsg & msg, bool reading ) { 573 return GetGameLobby().GetMigrationGameData( msg, reading ); 574 } 575 576 /* 577 ======================== 578 idSessionLocal::GetMigrationGameDataUser 579 ======================== 580 */ 581 bool idSessionLocal::GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) { 582 if ( GetGameStateLobby().IsHost() ) { 583 return false; 584 } 585 586 return GetGameLobby().GetMigrationGameDataUser( lobbyUserID, msg, reading ); 587 } 588 589 590 /* 591 ======================== 592 idSessionLocal::GetMatchParamUpdate 593 ======================== 594 */ 595 bool idSessionLocal::GetMatchParamUpdate( int &peer, int &msg ){ 596 if ( storedPeer != -1 && storedMsgType != -1 ) { 597 peer = storedPeer; 598 msg = storedMsgType; 599 storedPeer = -1; 600 storedMsgType = -1; 601 return true; 602 } 603 return false; 604 } 605 606 607 608 /* 609 ======================== 610 idSessionLocal::UpdatePartyParms 611 612 Updates the party parameters when in a party lobby OR a game lobby in order to keep them always in sync. 613 ======================== 614 */ 615 void idSessionLocal::UpdatePartyParms( const idMatchParameters & p ) { 616 if ( ( GetState() != PARTY_LOBBY && GetState() != GAME_LOBBY ) || !GetPartyLobby().IsHost() ) { 617 return; 618 } 619 620 // NET_VERBOSE_PRINT( "NET: UpdatePartyParms\n" ); 621 622 GetPartyLobby().UpdateMatchParms( p ); 623 } 624 625 /* 626 ======================== 627 idSessionLocal::UpdateMatchParms 628 ======================== 629 */ 630 void idSessionLocal::UpdateMatchParms( const idMatchParameters & p ) { 631 if ( GetState() != GAME_LOBBY || !GetGameLobby().IsHost() ) { 632 return; 633 } 634 635 NET_VERBOSE_PRINT( "NET: UpdateMatchParms\n" ); 636 637 GetGameLobby().UpdateMatchParms( p ); 638 } 639 640 /* 641 ======================== 642 idSessionLocal::StartSessions 643 ======================== 644 */ 645 void idSessionLocal::StartSessions() { 646 if ( GetPartyLobby().lobbyBackend != NULL ) { 647 GetPartyLobby().lobbyBackend->StartSession(); 648 } 649 650 if ( GetGameLobby().lobbyBackend != NULL ) { 651 GetGameLobby().lobbyBackend->StartSession(); 652 } 653 654 SetLobbiesAreJoinable( false ); 655 } 656 657 /* 658 ======================== 659 idSessionLocal::EndSessions 660 ======================== 661 */ 662 void idSessionLocal::EndSessions() { 663 if ( GetPartyLobby().lobbyBackend != NULL ) { 664 GetPartyLobby().lobbyBackend->EndSession(); 665 } 666 667 if ( GetGameLobby().lobbyBackend != NULL ) { 668 GetGameLobby().lobbyBackend->EndSession(); 669 } 670 671 SetLobbiesAreJoinable( true ); 672 } 673 674 /* 675 ======================== 676 idSessionLocal::SetLobbiesAreJoinable 677 ======================== 678 */ 679 void idSessionLocal::SetLobbiesAreJoinable( bool joinable ) { 680 // NOTE - We don't manipulate the joinable state when we are supporting join in progress 681 // Lobbies will naturally be non searchable when there are no free slots 682 if ( GetPartyLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetPartyLobby().parms.matchFlags ) ) { 683 NET_VERBOSE_PRINT( "Party lobbyBackend SetIsJoinable: %d\n", joinable ); 684 GetPartyLobby().lobbyBackend->SetIsJoinable( joinable ); 685 } 686 687 if ( GetGameLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetGameLobby().parms.matchFlags ) ) { 688 GetGameLobby().lobbyBackend->SetIsJoinable( joinable ); 689 NET_VERBOSE_PRINT( "Game lobbyBackend SetIsJoinable: %d\n", joinable ); 690 691 } 692 } 693 694 /* 695 ======================== 696 idSessionLocal::MoveToMainMenu 697 ======================== 698 */ 699 void idSessionLocal::MoveToMainMenu() { 700 GetPartyLobby().Shutdown(); 701 GetGameLobby().Shutdown(); 702 GetGameStateLobby().Shutdown(); 703 SetState( STATE_IDLE ); 704 } 705 706 /* 707 ======================== 708 idSessionLocal::HandleVoiceRestrictionDialog 709 ======================== 710 */ 711 void idSessionLocal::HandleVoiceRestrictionDialog() { 712 // don't bother complaining about voice restrictions when in a splitscreen lobby 713 if ( MatchTypeIsLocal( GetActivePlatformLobby()->parms.matchFlags ) ) { 714 return; 715 } 716 717 // Pop a dialog up the first time we are in a lobby and have voice chat restrictions due to account privileges 718 if ( voiceChat != NULL && voiceChat->IsRestrictedByPrivleges() && !hasShownVoiceRestrictionDialog ) { 719 common->Dialog().AddDialog( GDM_VOICE_RESTRICTED, DIALOG_ACCEPT, NULL, NULL, false ); 720 hasShownVoiceRestrictionDialog = true; 721 } 722 } 723 724 /* 725 ======================== 726 idSessionLocal::WaitOnLobbyCreate 727 728 Called from State_Create_And_Move_To_Party_Lobby and State_Create_And_Move_To_Game_Lobby and State_Create_And_Move_To_Game_State_Lobby. 729 This function will create the lobby, then wait for it to either succeed or fail. 730 ======================== 731 */ 732 bool idSessionLocal::WaitOnLobbyCreate( idLobby & lobby ) { 733 734 assert( localState == STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY || localState == STATE_CREATE_AND_MOVE_TO_GAME_LOBBY || localState == STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY ); 735 assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_NONE ); 736 737 if ( lobby.GetState() == idLobby::STATE_FAILED ) { 738 NET_VERBOSE_PRINT( "NET: idSessionLocal::WaitOnLobbyCreate lobby.GetState() == idLobby::STATE_FAILED (%s)\n", lobby.GetLobbyName() ); 739 // If we failed to create a lobby, assume connection to backend service was lost 740 MoveToMainMenu(); 741 common->Dialog().ClearDialogs( true ); 742 common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, true, "", 0, true ); 743 return false; 744 } 745 746 if ( DetectDisconnectFromService( true ) ) { 747 return false; 748 } 749 750 if ( lobby.GetState() != idLobby::STATE_IDLE ) { 751 return false; // Valid but busy 752 } 753 754 NET_VERBOSE_PRINT( "NET: idSessionLocal::WaitOnLobbyCreate SUCCESS (%s)\n", lobby.GetLobbyName() ); 755 756 return true; 757 } 758 759 /* 760 ======================== 761 idSessionLocal::DetectDisconnectFromService 762 Called from CreateMatch/CreatePartyLobby/FindOrCreateMatch state machines 763 ======================== 764 */ 765 bool idSessionLocal::DetectDisconnectFromService( bool cancelAndShowMsg ) { 766 const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 ); 767 768 // If we are taking too long, cancel the connection 769 if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) { 770 if ( Sys_Milliseconds() - connectTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) { 771 NET_VERBOSE_PRINT( "NET: idSessionLocal::DetectDisconnectFromService timed out\n" ); 772 if ( cancelAndShowMsg ) { 773 MoveToMainMenu(); 774 common->Dialog().ClearDialogs( true ); 775 common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); 776 } 777 778 return true; 779 } 780 } 781 782 return false; 783 } 784 785 /* 786 ======================== 787 idSessionLocal::HandleConnectionFailed 788 Called anytime a connection fails, and does the right thing. 789 ======================== 790 */ 791 void idSessionLocal::HandleConnectionFailed( idLobby & lobby, bool wasFull ) { 792 assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); 793 assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT ); 794 bool canPlayOnline = true; 795 796 // Check for online status (this is only a problem on the PS3 at the moment. The 360 LIVE system handles this for us 797 if ( GetSignInManager().GetMasterLocalUser() != NULL ) { 798 canPlayOnline = GetSignInManager().GetMasterLocalUser()->CanPlayOnline(); 799 } 800 801 if ( connectType == CONNECT_FIND_OR_CREATE ) { 802 // Clear the "Lobby was Full" dialog in case it's up 803 // We only want to see this msg when doing a direct connect (CONNECT_DIRECT) 804 common->Dialog().ClearDialog( GDM_LOBBY_FULL ); 805 806 assert( localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); 807 assert( lobby.lobbyType == idLobby::TYPE_GAME ); 808 if ( !lobby.ConnectToNextSearchResult() ) { 809 CreateMatch( GetGameLobby().parms ); // Assume any time we are connecting to a game lobby, it is from a FindOrCreateMatch call, so create a match 810 } 811 } else if ( connectType == CONNECT_DIRECT ) { 812 if ( localState == STATE_CONNECT_AND_MOVE_TO_GAME && GetPartyLobby().IsPeer() ) { 813 int flags = GetPartyLobby().parms.matchFlags; 814 815 if ( MatchTypeIsOnline( flags ) && ( flags & MATCH_REQUIRE_PARTY_LOBBY ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) { 816 // We get here when our party host told us to connect to a game, but the game didn't exist. 817 // Just drop back to the party lobby and wait for further orders. 818 SetState( STATE_PARTY_LOBBY_PEER ); 819 return; 820 } 821 } 822 823 if ( wasFull ) { 824 common->Dialog().AddDialog( GDM_LOBBY_FULL, DIALOG_ACCEPT, NULL, NULL, false ); 825 } else if ( !canPlayOnline ) { 826 common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT, NULL, NULL, false ); 827 } else { 828 // TEMP HACK: We detect the steam lobby is full in idLobbyBackendWin, and then STATE_FAILED, which brings us here. Need to find a way to notify 829 // session local that the game was full so we don't do this check here 830 // eeubanks: Pollard, how do you think we should handle this? 831 if ( !common->Dialog().HasDialogMsg( GDM_LOBBY_FULL, NULL ) ) { 832 common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false ); 833 } 834 } 835 MoveToMainMenu(); 836 } else { 837 // Shouldn't be possible, but just in case 838 MoveToMainMenu(); 839 } 840 } 841 842 /* 843 ======================== 844 idSessionLocal::HandleConnectAndMoveToLobby 845 Called from State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game 846 ======================== 847 */ 848 bool idSessionLocal::HandleConnectAndMoveToLobby( idLobby & lobby ) { 849 assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME || localState == STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); 850 assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT ); 851 852 if ( lobby.GetState() == idLobby::STATE_FAILED ) { 853 // If we get here, we were trying to connect to a lobby (from state State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game) 854 HandleConnectionFailed( lobby, false ); 855 return true; 856 } 857 858 if ( lobby.GetState() != idLobby::STATE_IDLE ) { 859 return HandlePackets(); // Valid but busy 860 } 861 862 assert( !GetPartyLobby().waitForPartyOk ); 863 864 // 865 // Past this point, we've connected to the lobby 866 // 867 868 // If we are connecting to a game lobby, see if we need to keep waiting as either a host or peer while we're confirming all party members made it 869 if ( lobby.lobbyType == idLobby::TYPE_GAME ) { 870 if ( GetPartyLobby().IsHost() ) { 871 // As a host, wait until all party members make it 872 assert( !GetGameLobby().waitForPartyOk ); 873 874 const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000; 875 876 if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) { 877 // Took too long, move to next result, or create a game instead 878 HandleConnectionFailed( lobby, false ); 879 return true; 880 } 881 882 int numUsersIn = 0; 883 884 for ( int i = 0; i < GetPartyLobby().GetNumLobbyUsers(); i++ ) { 885 886 if ( net_testPartyMemberConnectFail.GetInteger() == i ) { 887 continue; 888 } 889 890 bool foundUser = false; 891 892 lobbyUser_t * partyUser = GetPartyLobby().GetLobbyUser( i ); 893 894 for ( int j = 0; j < GetGameLobby().GetNumLobbyUsers(); j++ ) { 895 lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( j ); 896 897 if ( GetGameLobby().IsSessionUserLocal( gameUser ) || gameUser->address.Compare( partyUser->address, true ) ) { 898 numUsersIn++; 899 foundUser = true; 900 break; 901 } 902 } 903 904 assert( !GetPartyLobby().IsSessionUserIndexLocal( i ) || foundUser ); 905 } 906 907 if ( numUsersIn != GetPartyLobby().GetNumLobbyUsers() ) { 908 return HandlePackets(); // All users not in, keep waiting until all user make it, or we time out 909 } 910 911 NET_VERBOSE_PRINT( "NET: All party members made it into the game lobby.\n" ); 912 913 // Let all the party members know everyone made it, and it's ok to stay at this server 914 for ( int i = 0; i < GetPartyLobby().peers.Num(); i++ ) { 915 if ( GetPartyLobby().peers[ i ].IsConnected() ) { 916 GetPartyLobby().QueueReliableMessage( i, idLobby::RELIABLE_PARTY_CONNECT_OK ); 917 } 918 } 919 } else { 920 if ( !verify ( lobby.host != -1 ) ) { 921 MoveToMainMenu(); 922 connectType = CONNECT_NONE; 923 return false; 924 } 925 926 // As a peer, wait for server to tell us everyone made it 927 if ( GetGameLobby().waitForPartyOk ) { 928 const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000; 929 930 if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) { 931 GetGameLobby().waitForPartyOk = false; // Just connect to this game lobby if we haven't heard from the party host for the entire timeout duration 932 } 933 } 934 935 if ( GetGameLobby().waitForPartyOk ) { 936 return HandlePackets(); // Waiting on party host to tell us everyone made it 937 } 938 } 939 } 940 941 // Success 942 switch ( lobby.lobbyType ) { 943 case idLobby::TYPE_PARTY: 944 SetState( STATE_PARTY_LOBBY_PEER ); 945 break; 946 case idLobby::TYPE_GAME: 947 SetState( STATE_GAME_LOBBY_PEER ); 948 break; 949 case idLobby::TYPE_GAME_STATE: 950 waitingOnGameStateMembersToJoinTime = Sys_Milliseconds(); 951 // As a host of the game lobby, it's our duty to notify our members to also join this game state lobby 952 GetGameLobby().SendMembersToLobby( GetGameStateLobby(), false ); 953 SetState( STATE_GAME_STATE_LOBBY_PEER ); 954 break; 955 } 956 957 connectType = CONNECT_NONE; 958 959 return false; 960 } 961 962 /* 963 ======================== 964 idSessionLocal::State_Create_And_Move_To_Party_Lobby 965 ======================== 966 */ 967 bool idSessionLocal::State_Create_And_Move_To_Party_Lobby() { 968 if ( WaitOnLobbyCreate( GetPartyLobby() ) ) { 969 970 if ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) { 971 // If this party lobby was for a placeholder, continue on with either finding or creating a game lobby 972 if ( connectType == CONNECT_FIND_OR_CREATE ) { 973 FindOrCreateMatch( GetPartyLobby().parms ); 974 return true; 975 } else if ( connectType == CONNECT_NONE ) { 976 CreateMatch( GetPartyLobby().parms ); 977 return true; 978 } 979 } 980 981 // Success 982 SetState( STATE_PARTY_LOBBY_HOST ); 983 984 return true; 985 } 986 987 return HandlePackets(); // Valid but busy 988 } 989 990 /* 991 ======================== 992 idSessionLocal::State_Create_And_Move_To_Game_Lobby 993 ======================== 994 */ 995 bool idSessionLocal::State_Create_And_Move_To_Game_Lobby() { 996 997 if ( WaitOnLobbyCreate( GetGameLobby() ) ) { 998 // Success 999 SetState( STATE_GAME_LOBBY_HOST ); 1000 1001 // Now that we've created our game lobby, send our own party users to it 1002 // NOTE - We pass in false to wait on party members since we are the host, and we know they can connect to us 1003 GetPartyLobby().SendMembersToLobby( GetGameLobby(), false ); 1004 return true; 1005 } 1006 1007 return false; 1008 } 1009 1010 /* 1011 ======================== 1012 idSessionLocal::State_Create_And_Move_To_Game_State_Lobby 1013 ======================== 1014 */ 1015 bool idSessionLocal::State_Create_And_Move_To_Game_State_Lobby() { 1016 1017 if ( WaitOnLobbyCreate( GetGameStateLobby() ) ) { 1018 // Success 1019 SetState( STATE_GAME_STATE_LOBBY_HOST ); 1020 1021 // Now that we've created our game state lobby, send our own game users to it 1022 // NOTE - We pass in false to wait on party members since we are the host, and we know they can connect to us 1023 GetGameLobby().SendMembersToLobby( GetGameStateLobby(), false ); 1024 1025 // If we are the host of a game lobby, we know we are not using dedicated servers, so we want to start the match immediately 1026 // as soon as we detect all users have connected. 1027 if ( GetGameLobby().IsHost() ) { 1028 waitingOnGameStateMembersToJoinTime = Sys_Milliseconds(); 1029 } 1030 1031 return true; 1032 } 1033 1034 return false; 1035 } 1036 1037 /* 1038 ======================== 1039 idSessionLocal::State_Find_Or_Create_Match 1040 ======================== 1041 */ 1042 bool idSessionLocal::State_Find_Or_Create_Match() { 1043 assert( connectType == CONNECT_FIND_OR_CREATE ); 1044 1045 if ( GetGameLobby().GetState() == idLobby::STATE_FAILED ) { 1046 // Failed to find any games. Create one instead (we're assuming this always gets called from FindOrCreateMatch 1047 CreateMatch( GetGameLobby().parms ); 1048 return true; 1049 } 1050 1051 if ( DetectDisconnectFromService( true ) ) { 1052 return false; 1053 } 1054 1055 if ( GetGameLobby().GetState() != idLobby::STATE_IDLE ) { 1056 return HandlePackets(); // Valid but busy 1057 } 1058 1059 // Done searching, connect to the first search result 1060 if ( !GetGameLobby().ConnectToNextSearchResult() ) { 1061 // Failed to find any games. Create one instead (we're assuming this always gets called from FindOrCreateMatch 1062 CreateMatch( GetGameLobby().parms ); 1063 return true; 1064 } 1065 1066 SetState( STATE_CONNECT_AND_MOVE_TO_GAME ); 1067 1068 return true; 1069 } 1070 1071 /* 1072 ======================== 1073 idSessionLocal::State_Connect_And_Move_To_Party 1074 ======================== 1075 */ 1076 bool idSessionLocal::State_Connect_And_Move_To_Party() { 1077 return HandleConnectAndMoveToLobby( GetPartyLobby() ); 1078 } 1079 1080 /* 1081 ======================== 1082 idSessionLocal::State_Connect_And_Move_To_Game 1083 ======================== 1084 */ 1085 bool idSessionLocal::State_Connect_And_Move_To_Game() { 1086 return HandleConnectAndMoveToLobby( GetGameLobby() ); 1087 } 1088 1089 /* 1090 ======================== 1091 idSessionLocal::State_Connect_And_Move_To_Game_State 1092 ======================== 1093 */ 1094 bool idSessionLocal::State_Connect_And_Move_To_Game_State() { 1095 return HandleConnectAndMoveToLobby( GetGameStateLobby() ); 1096 } 1097 1098 /* 1099 ======================== 1100 idSessionLocal::State_InGame 1101 ======================== 1102 */ 1103 bool idSessionLocal::State_InGame() { 1104 return HandlePackets(); 1105 } 1106 1107 /* 1108 ======================== 1109 idSessionLocal::State_Loading 1110 ======================== 1111 */ 1112 bool idSessionLocal::State_Loading() { 1113 1114 HandlePackets(); 1115 1116 if ( !GetActingGameStateLobby().loaded ) { 1117 return false; 1118 } 1119 1120 SetVoiceGroupsToTeams(); 1121 1122 if ( GetActingGameStateLobby().IsHost() ) { 1123 bool everyoneLoaded = true; 1124 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 1125 idLobby::peer_t & peer = GetActingGameStateLobby().peers[p]; 1126 1127 if ( !peer.IsConnected() ) { 1128 continue; // We don't care about peers that aren't connected as a game session 1129 } 1130 1131 if ( !peer.loaded ) { 1132 everyoneLoaded = false; 1133 continue; // Don't waste time sending resources to a peer who hasn't loaded the map yet 1134 } 1135 1136 if ( GetActingGameStateLobby().SendResources( p ) ) { 1137 everyoneLoaded = false; 1138 1139 // if client is taking a LONG time to load up - give them the boot: they're just holding up the lunch line. Useful for loose assets playtesting. 1140 int time = Sys_Milliseconds(); 1141 int maxLoadTime = net_maxLoadResourcesTimeInSeconds.GetInteger(); 1142 if ( maxLoadTime > 0 && peer.startResourceLoadTime + SEC2MS( maxLoadTime ) < time ) { 1143 NET_VERBOSERESOURCE_PRINT( "NET: dropping client %i - %s because they took too long to load resources.\n Check 'net_maxLoadResourcesTimeInSeconds' to adjust the time allowed.\n", p, GetPeerName( p ) ); 1144 GetActingGameStateLobby().DisconnectPeerFromSession( p ); 1145 continue; 1146 } 1147 } 1148 } 1149 if ( !everyoneLoaded ) { 1150 return false; 1151 } 1152 } else { 1153 // not sure how we got there, but we won't be receiving anything that could get us out of this state anymore 1154 // possible step towards fixing the join stalling/disconnect problems 1155 if ( GetActingGameStateLobby().peers.Num() == 0 ) { 1156 NET_VERBOSE_PRINT( "NET: no peers in idSessionLocal::State_Loading - giving up\n" ); 1157 MoveToMainMenu(); 1158 } 1159 // need at least a peer with a real connection 1160 bool haveOneGoodPeer = false; 1161 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 1162 if ( GetActingGameStateLobby().peers[p].IsConnected() ) { 1163 haveOneGoodPeer = true; 1164 break; 1165 } 1166 } 1167 if ( !haveOneGoodPeer ) { 1168 NET_VERBOSE_PRINT( "NET: no good peers in idSessionLocal::State_Loading - giving up\n" ); 1169 MoveToMainMenu(); 1170 } 1171 1172 return false; 1173 } 1174 1175 GetActingGameStateLobby().ResetBandwidthStats(); 1176 1177 // if we got here then we're the host and everyone indicated map load finished 1178 NET_VERBOSE_PRINT( "NET: (loading) Starting Game\n" ); 1179 SetState( STATE_INGAME ); // NOTE - Only the host is in-game at this point, all peers will start becoming in-game when they receive their first full snap 1180 return true; 1181 } 1182 1183 /* 1184 ======================== 1185 idSessionLocal::State_Busy 1186 ======================== 1187 */ 1188 bool idSessionLocal::State_Busy() { 1189 idLobby * activeLobby = GetActivePlatformLobby(); 1190 if ( activeLobby == NULL ) { 1191 idLib::Warning("No active session lobby when idSessionLocal::State_Busy called"); 1192 return false; 1193 } 1194 1195 if ( activeLobby->bandwidthChallengeFinished ) { 1196 // Start loading 1197 NET_VERBOSE_PRINT( "NET: Bandwidth test finished - Start loading\n" ); 1198 StartLoading(); 1199 } 1200 1201 return HandlePackets(); 1202 } 1203 1204 /* 1205 ======================== 1206 idSessionLocal::VerifySnapshotInitialState 1207 ======================== 1208 */ 1209 void idSessionLocal::VerifySnapshotInitialState() { 1210 // Verify that snapshot state is reset 1211 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 1212 if ( !GetActingGameStateLobby().peers[p].IsConnected() ) { 1213 assert( GetActingGameStateLobby().peers[p].snapProc == NULL ); 1214 continue; 1215 } 1216 1217 assert( GetActingGameStateLobby().peers[p].snapProc != NULL ); 1218 1219 if ( !verify( GetActingGameStateLobby().peers[p].needToSubmitPendingSnap == false ) ) { 1220 idLib::Error( "Invalid needToSubmitPendingSnap state\n" ); 1221 } 1222 if ( !verify( GetActingGameStateLobby().peers[p].snapProc->HasPendingSnap() == false ) ) { 1223 idLib::Error( "Invalid HasPendingSnap state\n" ); 1224 } 1225 if ( !verify( GetActingGameStateLobby().peers[p].snapProc->GetSnapSequence() == idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) ) { 1226 idLib::Error( "Invalid INITIAL_SNAP_SEQUENCE state %d for peer %d \n", GetActingGameStateLobby().peers[p].snapProc->GetSnapSequence(), p ); 1227 } 1228 if ( !verify( GetActingGameStateLobby().peers[p].snapProc->GetBaseSequence() == -1 ) ) { 1229 idLib::Error( "Invalid GetBaseSequence state\n" ); 1230 } 1231 } 1232 } 1233 1234 /* 1235 ======================== 1236 idSessionLocal::State_Party_Lobby_Host 1237 ======================== 1238 */ 1239 bool idSessionLocal::State_Party_Lobby_Host() { 1240 HandleVoiceRestrictionDialog(); 1241 return HandlePackets(); 1242 } 1243 1244 /* 1245 ======================== 1246 idSessionLocal::State_Game_Lobby_Host 1247 ======================== 1248 */ 1249 bool idSessionLocal::State_Game_Lobby_Host() { 1250 HandleVoiceRestrictionDialog(); 1251 return HandlePackets(); 1252 } 1253 1254 /* 1255 ======================== 1256 idSessionLocal::State_Game_State_Lobby_Host 1257 ======================== 1258 */ 1259 bool idSessionLocal::State_Game_State_Lobby_Host() { 1260 HandleVoiceRestrictionDialog(); 1261 1262 if ( waitingOnGameStateMembersToLeaveTime != 0 ) { 1263 1264 const int MAX_LEAVE_WAIT_TIME_IN_SECONDS = 5; 1265 1266 const bool forceDisconnectMembers = ( Sys_Milliseconds() - waitingOnGameStateMembersToLeaveTime ) > MAX_LEAVE_WAIT_TIME_IN_SECONDS * 1000; 1267 1268 // Check to see if all peers have finally left 1269 if ( GetGameStateLobby().GetNumConnectedPeers() == 0 || forceDisconnectMembers ) { 1270 1271 // 1272 // All peers left, we can stop waiting 1273 // 1274 1275 waitingOnGameStateMembersToLeaveTime = 0; 1276 1277 assert( !GetGameLobby().IsPeer() ); 1278 1279 if ( GetGameLobby().IsHost() ) { 1280 // If we aren't a dedicated game state host, then drop back to the game lobby as host 1281 GetGameStateLobby().Shutdown(); 1282 SetState( STATE_GAME_LOBBY_HOST ); 1283 } else { 1284 // A dedicated game state host will remain in State_Game_State_Lobby_Host mode while waiting for another set of users to join 1285 // DEDICATED_SERVER_FIXME: Notify master server we can server another game now 1286 GetGameStateLobby().DisconnectAllPeers(); 1287 } 1288 } 1289 } else { 1290 // When all the players from the game lobby are in the game state lobby, StartLoading 1291 if ( GetGameLobby().IsHost() ) { 1292 if ( GetGameStateLobby().GetNumLobbyUsers() == GetGameLobby().GetNumLobbyUsers() ) { 1293 waitingOnGameStateMembersToJoinTime = 0; 1294 StartLoading(); 1295 } 1296 } else { 1297 // The dedicated server host relies on the game host to say when all users are in 1298 if ( GetGameStateLobby().startLoadingFromHost ) { 1299 GetGameStateLobby().startLoadingFromHost = false; 1300 StartLoading(); 1301 } 1302 } 1303 } 1304 1305 return HandlePackets(); 1306 } 1307 1308 /* 1309 ======================== 1310 idSessionLocal::State_Party_Lobby_Peer 1311 ======================== 1312 */ 1313 bool idSessionLocal::State_Party_Lobby_Peer() { 1314 HandleVoiceRestrictionDialog(); 1315 return HandlePackets(); 1316 } 1317 1318 /* 1319 ======================== 1320 idSessionLocal::State_Game_Lobby_Peer 1321 ======================== 1322 */ 1323 bool idSessionLocal::State_Game_Lobby_Peer() { 1324 HandleVoiceRestrictionDialog(); 1325 bool saving = false; 1326 idPlayerProfile * profile = GetProfileFromMasterLocalUser(); 1327 if ( profile != NULL && ( profile->GetState() == idPlayerProfile::SAVING || profile->GetRequestedState() == idPlayerProfile::SAVE_REQUESTED ) ) { 1328 saving = true; 1329 } 1330 1331 if ( GetActingGameStateLobby().startLoadingFromHost && !saving ) { 1332 common->Dialog().ClearDialog( GDM_HOST_RETURNED_TO_LOBBY ); 1333 common->Dialog().ClearDialog( GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED ); 1334 1335 VerifySnapshotInitialState(); 1336 1337 // Set loading flag back to false 1338 GetActingGameStateLobby().startLoadingFromHost = false; 1339 1340 // Set state to loading 1341 SetState( STATE_LOADING ); 1342 1343 loadingID++; 1344 1345 return true; 1346 } 1347 1348 return HandlePackets(); 1349 } 1350 1351 /* 1352 ======================== 1353 idSessionLocal::State_Game_State_Lobby_Peer 1354 ======================== 1355 */ 1356 bool idSessionLocal::State_Game_State_Lobby_Peer() { 1357 // We are in charge of telling the dedicated host that all our members are in 1358 if ( GetGameLobby().IsHost() && waitingOnGameStateMembersToJoinTime != 0 ) { 1359 int foundMembers = 0; 1360 1361 for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); i++ ) { 1362 if ( GetGameStateLobby().GetLobbyUserByID( GetGameLobby().GetLobbyUser( i )->lobbyUserID, true ) != NULL ) { 1363 foundMembers++; 1364 } 1365 } 1366 1367 // Give all of our game members 10 seconds to join, otherwise start without them 1368 const int MAX_JOIN_WAIT_TIME_IN_SECONDS = 10; 1369 1370 const bool forceStart = ( Sys_Milliseconds() - waitingOnGameStateMembersToJoinTime ) > MAX_JOIN_WAIT_TIME_IN_SECONDS * 1000; 1371 1372 if ( foundMembers == GetGameLobby().GetNumLobbyUsers() || forceStart ) { 1373 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; 1374 1375 idBitMsg msg( buffer, sizeof( buffer ) ); 1376 1377 // Write match paramaters to the game state host, and tell him to start 1378 GetGameLobby().parms.Write( msg ); 1379 1380 // Tell the game state lobby host we are ready 1381 GetGameStateLobby().QueueReliableMessage( GetGameStateLobby().host, idLobby::RELIABLE_START_MATCH_GAME_LOBBY_HOST, msg.GetReadData(), msg.GetSize() ); 1382 1383 waitingOnGameStateMembersToJoinTime = 0; 1384 } 1385 } 1386 1387 return State_Game_Lobby_Peer(); 1388 } 1389 1390 /* 1391 ======================== 1392 idSessionLocal::~idSession 1393 ======================== 1394 */ 1395 idSession::~idSession() { 1396 delete signInManager; 1397 signInManager = NULL; 1398 delete saveGameManager; 1399 saveGameManager = NULL; 1400 delete dedicatedServerSearch; 1401 dedicatedServerSearch = NULL; 1402 } 1403 1404 idCVar net_verbose( "net_verbose", "0", CVAR_BOOL, "Print a bunch of message about the network session" ); 1405 idCVar net_verboseResource( "net_verboseResource", "0", CVAR_BOOL, "Prints a bunch of message about network resources" ); 1406 idCVar net_verboseReliable( "net_verboseReliable", "0", CVAR_BOOL, "Prints the more spammy messages about reliable network msgs" ); 1407 idCVar si_splitscreen( "si_splitscreen", "0", CVAR_INTEGER, "force splitscreen" ); 1408 1409 idCVar net_forceLatency( "net_forceLatency", "0", CVAR_INTEGER, "Simulate network latency (milliseconds round trip time - applied equally on the receive and on the send)" ); 1410 idCVar net_forceDrop( "net_forceDrop", "0", CVAR_INTEGER, "Percentage chance of simulated network packet loss" ); 1411 idCVar net_forceUpstream( "net_forceUpstream", "0", CVAR_FLOAT, "Force a maximum upstream in kB/s (256kbps <-> 32kB/s)" ); // I would much rather deal in kbps but most of the code is written in bytes .. 1412 idCVar net_forceUpstreamQueue( "net_forceUpstreamQueue", "64", CVAR_INTEGER, "How much data is queued when enforcing upstream (in kB)" ); 1413 idCVar net_verboseSimulatedTraffic( "net_verboseSimulatedTraffic", "0", CVAR_BOOL, "Print some stats about simulated traffic (net_force* cvars)" ); 1414 1415 /* 1416 ======================== 1417 idSessionLocal::Initialize 1418 ======================== 1419 */ 1420 void idSessionLocal::Initialize() { 1421 } 1422 1423 /* 1424 ======================== 1425 idSessionLocal::Shutdown 1426 ======================== 1427 */ 1428 void idSessionLocal::Shutdown() { 1429 } 1430 1431 /* 1432 ======================== 1433 idSession interface semi-common between platforms (#ifdef's in sys_session_local.cpp) 1434 ======================== 1435 */ 1436 1437 idCVar com_deviceZeroOverride( "com_deviceZeroOverride", "-1", CVAR_INTEGER, "change input routing for device 0 to poll a different device" ); 1438 idCVar mp_bot_input_override( "mp_bot_input_override", "-1", CVAR_INTEGER, "Override local input routing for bot control" ); 1439 1440 /* 1441 ======================== 1442 idSessionLocal::GetInputRouting 1443 This function sets up inputRouting to be a mapping from inputDevice index to session user index. 1444 ======================== 1445 */ 1446 int idSessionLocal::GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ) { 1447 1448 int numLocalUsers = 0; 1449 for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { 1450 inputRouting[i] = -1; 1451 } 1452 1453 for ( int i = 0; i < GetActingGameStateLobby().GetNumLobbyUsers(); i++ ) { 1454 if ( GetActingGameStateLobby().IsSessionUserIndexLocal( i ) ) { 1455 1456 // Find the local user that this session user maps to 1457 const idLocalUser * localUser = GetActingGameStateLobby().GetLocalUserFromLobbyUserIndex( i ); 1458 1459 if ( localUser != NULL ) { 1460 int localDevice = localUser->GetInputDevice(); 1461 if ( localDevice == 0 && com_deviceZeroOverride.GetInteger() > 0 ) { 1462 localDevice = com_deviceZeroOverride.GetInteger(); 1463 } 1464 assert( localDevice < MAX_INPUT_DEVICES ); 1465 // Route the input device that this local user is mapped to 1466 assert( inputRouting[localDevice] == -1 ); // Make sure to only initialize each entry once 1467 inputRouting[localDevice] = i; 1468 1469 if ( mp_bot_input_override.GetInteger() >= 0 ) { 1470 inputRouting[localDevice] = mp_bot_input_override.GetInteger(); 1471 } 1472 1473 numLocalUsers++; 1474 } 1475 } 1476 } 1477 1478 // For testing swapping controllers 1479 if ( si_splitscreen.GetInteger() == 2 && numLocalUsers == 2 ) { 1480 SwapValues( inputRouting[0], inputRouting[1] ); 1481 } 1482 1483 return numLocalUsers; 1484 } 1485 1486 /* 1487 ======================== 1488 idSessionLocal::EndMatch 1489 EndMatch is meant for the host to cleanly end a match and return to the lobby page 1490 ======================== 1491 */ 1492 void idSessionLocal::EndMatch( bool premature /*=false*/ ) { 1493 if ( verify( GetActingGameStateLobby().IsHost() ) ) { 1494 // Host quits back to game lobby, and will notify peers internally to do the same 1495 EndMatchInternal( premature ); 1496 } 1497 } 1498 1499 /* 1500 ======================== 1501 idSessionLocal::EndMatch 1502 this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time 1503 ======================== 1504 */ 1505 void idSessionLocal::MatchFinished( ) { 1506 if ( verify( GetActingGameStateLobby().IsHost() ) ) { 1507 // host is putting up end game stats make sure other peers know and clear migration data 1508 MatchFinishedInternal(); 1509 } 1510 } 1511 1512 /* 1513 ======================== 1514 idSessionLocal::QuitMatch 1515 QuitMatch is considered a premature ending of a match, and does the right thing depending on whether the host or peer is quitting 1516 ======================== 1517 */ 1518 void idSessionLocal::QuitMatch() { 1519 if ( GetActingGameStateLobby().IsHost() && !MatchTypeIsRanked( GetActingGameStateLobby().parms.matchFlags ) ) { 1520 EndMatch( true ); // When host quits private match, takes members back to game lobby 1521 } else { 1522 // Quitting a public match (or not being a host) before it ends takes you to an empty party lobby 1523 CreatePartyLobby( GetActingGameStateLobby().parms ); 1524 } 1525 } 1526 1527 /* 1528 ======================== 1529 idSessionLocal::QuitMatchToTitle 1530 QuitMatchToTitle will forcefully quit the match and return to the title screen. 1531 ======================== 1532 */ 1533 void idSessionLocal::QuitMatchToTitle() { 1534 MoveToMainMenu(); 1535 } 1536 1537 /* 1538 ======================== 1539 idSessionLocal::ClearMigrationState 1540 ======================== 1541 */ 1542 void idSessionLocal::ClearMigrationState() { 1543 // We are ending the match without migration, so clear that state 1544 GetPartyLobby().ResetAllMigrationState(); 1545 GetGameLobby().ResetAllMigrationState(); 1546 } 1547 1548 /* 1549 ======================== 1550 idSessionLocal::EndMatchInternal 1551 ======================== 1552 */ 1553 void idSessionLocal::EndMatchInternal( bool premature/*=false*/ ) { 1554 assert( GetGameStateLobby().IsLobbyActive() == net_useGameStateLobby.GetBool() ); 1555 1556 ClearVoiceGroups(); 1557 1558 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 1559 // If we are the host, increment the session ID. The client will use a rolling check to catch it 1560 if ( GetActingGameStateLobby().IsHost() ) { 1561 if ( GetActingGameStateLobby().peers[p].IsConnected() ) { 1562 if ( GetActingGameStateLobby().peers[p].packetProc != NULL ) { 1563 GetActingGameStateLobby().peers[p].packetProc->VerifyEmptyReliableQueue( idLobby::RELIABLE_GAME_DATA, idLobby::RELIABLE_DUMMY_MSG ); 1564 } 1565 GetActingGameStateLobby().peers[p].sessionID = GetActingGameStateLobby().IncrementSessionID( GetActingGameStateLobby().peers[p].sessionID ); 1566 } 1567 } 1568 GetActingGameStateLobby().peers[p].ResetMatchData(); 1569 } 1570 1571 GetActingGameStateLobby().snapDeltaAckQueue.Clear(); 1572 1573 GetActingGameStateLobby().loaded = false; 1574 1575 gameLobbyWasCoalesced = false; // Reset this back to false. We use this so the lobby code doesn't randomly choose a map when we coalesce 1576 numFullSnapsReceived = 0; 1577 1578 ClearMigrationState(); 1579 1580 if ( GetActingGameStateLobby().IsLobbyActive() && ( GetActingGameStateLobby().GetMatchParms().matchFlags & MATCH_REQUIRE_PARTY_LOBBY ) ) { 1581 // All peers need to remove disconnected users to stay in sync 1582 GetActingGameStateLobby().CompactDisconnectedUsers(); 1583 1584 // Go back to the game lobby 1585 if ( GetActingGameStateLobby().IsHost() ) { 1586 // We want the game state host to go back to STATE_GAME_STATE_LOBBY_HOST, so he can wait on all his game state peers to leave 1587 SetState( GetGameStateLobby().IsHost() ? STATE_GAME_STATE_LOBBY_HOST : STATE_GAME_LOBBY_HOST ); // We want the dedicated host to go back to STATE_GAME_STATE_LOBBY_HOST 1588 } else { 1589 SetState( STATE_GAME_LOBBY_PEER ); 1590 } 1591 } else { 1592 SetState( STATE_IDLE ); 1593 } 1594 1595 if ( GetActingGameStateLobby().IsHost() ) { 1596 // Send a reliable msg to all peers to also "EndMatch" 1597 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 1598 GetActingGameStateLobby().QueueReliableMessage( p, premature ? idLobby::RELIABLE_ENDMATCH_PREMATURE : idLobby::RELIABLE_ENDMATCH ); 1599 } 1600 } else if ( premature ) { 1601 // Notify client that host left early and thats why we are back in the lobby 1602 const bool stats = MatchTypeHasStats( GetActingGameStateLobby().GetMatchParms().matchFlags ) && ( GetFlushedStats() == false ); 1603 common->Dialog().AddDialog( stats ? GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED : GDM_HOST_RETURNED_TO_LOBBY, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); 1604 } 1605 1606 if ( GetGameStateLobby().IsLobbyActive() ) { 1607 if ( GetGameStateLobby().IsHost() ) { 1608 // As a game state host, keep the lobby around, so we can make sure we know when everyone leaves (which means they got the reliable msg to EndMatch) 1609 waitingOnGameStateMembersToLeaveTime = Sys_Milliseconds(); 1610 } else if ( GetGameStateLobby().IsPeer() ) { 1611 // Game state lobby peers should disconnect now 1612 GetGameStateLobby().Shutdown(); 1613 } 1614 } 1615 } 1616 1617 /* 1618 ======================== 1619 idSessionLocal::MatchFinishedInternal 1620 ======================== 1621 */ 1622 void idSessionLocal::MatchFinishedInternal() { 1623 ClearMigrationState(); 1624 1625 if ( GetActingGameStateLobby().IsHost() ) { 1626 // Send a reliable msg to all peers to also "EndMatch" 1627 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 1628 GetActingGameStateLobby().QueueReliableMessage( p, idLobby::RELIABLE_MATCHFINISHED ); 1629 } 1630 } 1631 } 1632 1633 /* 1634 ======================== 1635 idSessionLocal::EndMatchForMigration 1636 ======================== 1637 */ 1638 void idSessionLocal::EndMatchForMigration() { 1639 ClearVoiceGroups(); 1640 } 1641 1642 /* 1643 ======================== 1644 idSessionLocal::ShouldHavePartyLobby 1645 ======================== 1646 */ 1647 bool idSessionLocal::ShouldHavePartyLobby() { 1648 if ( GetActivePlatformLobby() == NULL ) { 1649 return false; 1650 } 1651 1652 idMatchParameters & parms = GetActivePlatformLobby()->parms; 1653 1654 int flags = parms.matchFlags; 1655 1656 // Don't we always have a party lobby if we're online? At least in Doom 3? 1657 return MatchTypeIsOnline( flags ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ); 1658 } 1659 1660 /* 1661 ======================== 1662 idSessionLocal::ValidateLobbies 1663 Determines if any of the session instances need to become the host 1664 ======================== 1665 */ 1666 void idSessionLocal::ValidateLobbies() { 1667 if ( localState == STATE_PRESS_START || localState == STATE_IDLE ) { 1668 // At press start or main menu, don't do anything 1669 return; 1670 } 1671 1672 if ( GetActivePlatformLobby() == NULL ) { 1673 // If we're in between lobbies, don't do anything yet (the state transitioning code will handle error cases) 1674 return; 1675 } 1676 1677 // Validate lobbies that should be alive and active 1678 if ( ShouldHavePartyLobby() && GetState() >= idSession::PARTY_LOBBY ) { 1679 ValidateLobby( GetPartyLobby() ); 1680 } 1681 if ( GetState() >= idSession::GAME_LOBBY && !net_headlessServer.GetBool() ) { 1682 ValidateLobby( GetGameLobby() ); 1683 } 1684 } 1685 1686 /* 1687 ======================== 1688 idSessionLocal::ValidateLobby 1689 ======================== 1690 */ 1691 void idSessionLocal::ValidateLobby( idLobby & lobby ) { 1692 if ( lobby.lobbyBackend == NULL || lobby.lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED || lobby.GetState() == idLobby::STATE_FAILED ) { 1693 NET_VERBOSE_PRINT( "NET: ValidateLobby: FAILED (lobbyType = %i, state = %s)\n", lobby.lobbyType, stateToString[ localState ] ); 1694 if ( lobby.failedReason == idLobby::FAILED_MIGRATION_CONNECT_FAILED || lobby.failedReason == idLobby::FAILED_CONNECT_FAILED ) { 1695 MoveToMainMenu(); 1696 common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false ); // The game session no longer exists 1697 } else { 1698 // If the lobbyBackend goes bad under our feet for no known reason, assume we lost connection to the back end service 1699 MoveToMainMenu(); 1700 common->Dialog().ClearDialogs( true ); 1701 common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false ); // Lost connection to XBox LIVE 1702 } 1703 } 1704 } 1705 1706 /* 1707 ======================== 1708 idSessionLocal::Pump 1709 ======================== 1710 */ 1711 void idSessionLocal::Pump() { 1712 SCOPED_PROFILE_EVENT( "Session::Pump" ); 1713 1714 static int lastPumpTime = -1; 1715 1716 const int time = Sys_Milliseconds(); 1717 const int elapsedPumpSeconds = ( time - lastPumpTime ) / 1000; 1718 1719 if ( lastPumpTime != -1 && elapsedPumpSeconds > 2 ) { 1720 idLib::Warning( "idSessionLocal::Pump was not called for %i seconds", elapsedPumpSeconds ); 1721 } 1722 1723 lastPumpTime = time; 1724 1725 if ( net_migrateHost.GetInteger() >= 0 ) { 1726 if ( net_migrateHost.GetInteger() <= 2 ) { 1727 if ( net_migrateHost.GetInteger() == 0 ) { 1728 GetPartyLobby().PickNewHost( true, true ); 1729 } else { 1730 GetGameLobby().PickNewHost( true, true ); 1731 } 1732 } else { 1733 GetPartyLobby().PickNewHost( true, true ); 1734 GetGameLobby().PickNewHost( true, true ); 1735 } 1736 net_migrateHost.SetInteger( -1 ); 1737 } 1738 1739 PlatformPump(); 1740 1741 if ( HasAchievementSystem() ) { 1742 GetAchievementSystem().Pump(); 1743 } 1744 1745 // Send any voice packets if it's time 1746 SendVoiceAudio(); 1747 1748 bool shouldContinue = true; 1749 1750 while ( shouldContinue ) { 1751 // Each iteration, validate the session instances 1752 ValidateLobbies(); 1753 1754 // Pump state 1755 shouldContinue = HandleState(); 1756 1757 // Pump lobbies 1758 PumpLobbies(); 1759 } 1760 1761 if ( GetPartyLobby().lobbyBackend != NULL ) { 1762 // Make sure game properties aren't set on the lobbyBackend if we aren't in a game lobby. 1763 // This is so we show up properly in search results in Play with Friends option 1764 GetPartyLobby().lobbyBackend->SetInGame( GetGameLobby().IsLobbyActive() ); 1765 1766 // Temp location 1767 UpdateMasterUserHeadsetState(); 1768 } 1769 1770 // Do some last minute checks, make sure everything about the current state and lobbyBackend state is valid, otherwise, take action 1771 ValidateLobbies(); 1772 1773 GetActingGameStateLobby().UpdateSnaps(); 1774 1775 idLobby * activeLobby = GetActivePlatformLobby(); 1776 1777 // Pump pings for the active lobby 1778 if ( activeLobby != NULL ) { 1779 activeLobby->PumpPings(); 1780 } 1781 1782 // Pump packet processing for all lobbies 1783 GetPartyLobby().PumpPackets(); 1784 GetGameLobby().PumpPackets(); 1785 GetGameStateLobby().PumpPackets(); 1786 1787 int currentTime = Sys_Milliseconds(); 1788 1789 const int SHOW_MIGRATING_INFO_IN_SECONDS = 3; // Show for at least this long once we start showing it 1790 1791 if ( ShouldShowMigratingDialog() ) { 1792 showMigratingInfoStartTime = currentTime; 1793 } else if ( showMigratingInfoStartTime > 0 && ( ( currentTime - showMigratingInfoStartTime ) > SHOW_MIGRATING_INFO_IN_SECONDS * 1000 ) ) { 1794 showMigratingInfoStartTime = 0; 1795 } 1796 1797 bool isShowingMigrate = common->Dialog().HasDialogMsg( GDM_MIGRATING, NULL ); 1798 1799 if ( showMigratingInfoStartTime != 0 ) { 1800 if ( !isShowingMigrate ) { 1801 common->Dialog().AddDialog( GDM_MIGRATING, DIALOG_WAIT, NULL, NULL, false, "", 0, false, false, true ); 1802 } 1803 } else if ( isShowingMigrate ) { 1804 common->Dialog().ClearDialog( GDM_MIGRATING ); 1805 } 1806 1807 // Update possible pending invite 1808 UpdatePendingInvite(); 1809 1810 // Check to see if we should coalesce the lobby 1811 if ( nextGameCoalesceTime != 0 ) { 1812 1813 if ( GetGameLobby().IsLobbyActive() && 1814 GetGameLobby().IsHost() && 1815 GetState() == idSession::GAME_LOBBY && 1816 GetPartyLobby().GetNumLobbyUsers() <= 1 && 1817 GetGameLobby().GetNumLobbyUsers() == 1 && 1818 MatchTypeIsRanked( GetGameLobby().parms.matchFlags ) && 1819 Sys_Milliseconds() > nextGameCoalesceTime ) { 1820 1821 // If the player doesn't care about the mode or map, 1822 // make sure the search is broadened. 1823 idMatchParameters newGameParms = GetGameLobby().parms; 1824 newGameParms.gameMap = GAME_MAP_RANDOM; 1825 1826 // Assume that if the party lobby's mode is random, 1827 // the player chose "Quick Match" and doesn't care about the mode. 1828 // If the player chose "Find Match" and a specific mode, 1829 // the party lobby mode will be set to non-random. 1830 if ( GetPartyLobby().parms.gameMode == GAME_MODE_RANDOM ) { 1831 newGameParms.gameMode = GAME_MODE_RANDOM; 1832 } 1833 1834 FindOrCreateMatch( newGameParms ); 1835 1836 gameLobbyWasCoalesced = true; // Remember that this round was coalesced. We so this so main menu doesn't randomize the map, which looks odd 1837 nextGameCoalesceTime = 0; 1838 } 1839 } 1840 } 1841 1842 /* 1843 ======================== 1844 idSessionLocal::ProcessSnapAckQueue 1845 ======================== 1846 */ 1847 void idSessionLocal::ProcessSnapAckQueue() { 1848 if ( GetActingGameStateLobby().IsLobbyActive() ) { 1849 GetActingGameStateLobby().ProcessSnapAckQueue(); 1850 } 1851 } 1852 1853 /* 1854 ======================== 1855 idSessionLocal::UpdatePendingInvite 1856 ======================== 1857 */ 1858 void idSessionLocal::UpdatePendingInvite() { 1859 if ( pendingInviteMode == PENDING_INVITE_NONE ) { 1860 return; // No pending invite 1861 } 1862 1863 idLocalUser * masterLocalUser = signInManager->GetMasterLocalUser(); 1864 1865 if ( masterLocalUser == NULL && signInManager->IsDeviceBeingRegistered( pendingInviteDevice ) ) { 1866 idLib::Printf( "masterLocalUser == NULL\n" ); 1867 return; // Waiting on master to sign in to continue with invite 1868 } 1869 1870 const bool wasFromInvite = pendingInviteMode == PENDING_INVITE_WAITING; // Remember if this was a real invite, or a self invitation (matters when lobby is invite only) 1871 1872 // At this point, the invitee should be ready 1873 pendingInviteMode = PENDING_INVITE_NONE; 1874 1875 if ( masterLocalUser == NULL || masterLocalUser->GetInputDevice() != pendingInviteDevice || !masterLocalUser->IsOnline() ) { 1876 idLib::Printf( "ignoring invite - master local user is not setup properly\n" ); 1877 return; // If there is no master, if the invitee is not online, or different than the current master, then ignore invite 1878 } 1879 1880 // Clear any current dialogs, as we're going into a state which will be unstable for any current dialogs. 1881 // Do we want to throw an assert if a dialog is currently up? 1882 common->Dialog().ClearDialogs( true ); 1883 1884 // Everything looks good, let's join the party 1885 ConnectAndMoveToLobby( GetPartyLobby(), pendingInviteConnectInfo, wasFromInvite ); 1886 } 1887 1888 /* 1889 ======================== 1890 idSessionLocal::HandleState 1891 ======================== 1892 */ 1893 bool idSessionLocal::HandleState() { 1894 // Handle individual lobby states 1895 GetPartyLobby().Pump(); 1896 GetGameLobby().Pump(); 1897 GetGameStateLobby().Pump(); 1898 1899 // Let IsHost be authoritative on the qualification of peer/host state types 1900 if ( GetPartyLobby().IsHost() && localState == STATE_PARTY_LOBBY_PEER ) { 1901 SetState( STATE_PARTY_LOBBY_HOST ); 1902 } else if ( GetPartyLobby().IsPeer() && localState == STATE_PARTY_LOBBY_HOST ) { 1903 SetState( STATE_PARTY_LOBBY_PEER ); 1904 } 1905 1906 // Let IsHost be authoritative on the qualification of peer/host state types 1907 if ( GetGameLobby().IsHost() && localState == STATE_GAME_LOBBY_PEER ) { 1908 SetState( STATE_GAME_LOBBY_HOST ); 1909 } else if ( GetGameLobby().IsPeer() && localState == STATE_GAME_LOBBY_HOST ) { 1910 SetState( STATE_GAME_LOBBY_PEER ); 1911 } 1912 1913 switch ( localState ) { 1914 case STATE_PRESS_START: return false; 1915 case STATE_IDLE: HandlePackets(); return false; // Call handle packets, since packets from old sessions could still be in flight, which need to be emptied 1916 case STATE_PARTY_LOBBY_HOST: return State_Party_Lobby_Host(); 1917 case STATE_PARTY_LOBBY_PEER: return State_Party_Lobby_Peer(); 1918 case STATE_GAME_LOBBY_HOST: return State_Game_Lobby_Host(); 1919 case STATE_GAME_LOBBY_PEER: return State_Game_Lobby_Peer(); 1920 case STATE_GAME_STATE_LOBBY_HOST: return State_Game_State_Lobby_Host(); 1921 case STATE_GAME_STATE_LOBBY_PEER: return State_Game_State_Lobby_Peer(); 1922 case STATE_LOADING: return State_Loading(); 1923 case STATE_INGAME: return State_InGame(); 1924 case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return State_Create_And_Move_To_Party_Lobby(); 1925 case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return State_Create_And_Move_To_Game_Lobby(); 1926 case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return State_Create_And_Move_To_Game_State_Lobby(); 1927 case STATE_FIND_OR_CREATE_MATCH: return State_Find_Or_Create_Match(); 1928 case STATE_CONNECT_AND_MOVE_TO_PARTY: return State_Connect_And_Move_To_Party(); 1929 case STATE_CONNECT_AND_MOVE_TO_GAME: return State_Connect_And_Move_To_Game(); 1930 case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return State_Connect_And_Move_To_Game_State(); 1931 case STATE_BUSY: return State_Busy(); 1932 default: 1933 idLib::Error( "HandleState: Unknown state in idSessionLocal" ); 1934 } 1935 } 1936 1937 /* 1938 ======================== 1939 idSessionLocal::GetState 1940 ======================== 1941 */ 1942 idSessionLocal::sessionState_t idSessionLocal::GetState() const { 1943 // Convert our internal state to one of the external states 1944 switch ( localState ) { 1945 case STATE_PRESS_START: return PRESS_START; 1946 case STATE_IDLE: return IDLE; 1947 case STATE_PARTY_LOBBY_HOST: return PARTY_LOBBY; 1948 case STATE_PARTY_LOBBY_PEER: return PARTY_LOBBY; 1949 case STATE_GAME_LOBBY_HOST: return GAME_LOBBY; 1950 case STATE_GAME_LOBBY_PEER: return GAME_LOBBY; 1951 case STATE_GAME_STATE_LOBBY_HOST: return GAME_LOBBY; 1952 case STATE_GAME_STATE_LOBBY_PEER: return GAME_LOBBY; 1953 case STATE_LOADING: return LOADING; 1954 case STATE_INGAME: return INGAME; 1955 case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return CONNECTING; 1956 case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return CONNECTING; 1957 case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return CONNECTING; 1958 case STATE_FIND_OR_CREATE_MATCH: return SEARCHING; 1959 case STATE_CONNECT_AND_MOVE_TO_PARTY: return CONNECTING; 1960 case STATE_CONNECT_AND_MOVE_TO_GAME: return CONNECTING; 1961 case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return CONNECTING; 1962 case STATE_BUSY: return BUSY; 1963 default: { 1964 idLib::Error( "GetState: Unknown state in idSessionLocal" ); 1965 } 1966 }; 1967 } 1968 1969 const char * idSessionLocal::GetStateString() const { 1970 static const char * stateToString[] = { 1971 ASSERT_ENUM_STRING( STATE_PRESS_START, 0 ), 1972 ASSERT_ENUM_STRING( STATE_IDLE, 1 ), 1973 ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_HOST, 2 ), 1974 ASSERT_ENUM_STRING( STATE_PARTY_LOBBY_PEER, 3 ), 1975 ASSERT_ENUM_STRING( STATE_GAME_LOBBY_HOST, 4 ), 1976 ASSERT_ENUM_STRING( STATE_GAME_LOBBY_PEER, 5 ), 1977 ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_HOST, 6 ), 1978 ASSERT_ENUM_STRING( STATE_GAME_STATE_LOBBY_PEER, 7 ), 1979 ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, 8 ), 1980 ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, 9 ), 1981 ASSERT_ENUM_STRING( STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, 10 ), 1982 ASSERT_ENUM_STRING( STATE_FIND_OR_CREATE_MATCH, 11 ), 1983 ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_PARTY, 12 ), 1984 ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME, 13 ), 1985 ASSERT_ENUM_STRING( STATE_CONNECT_AND_MOVE_TO_GAME_STATE, 14 ), 1986 ASSERT_ENUM_STRING( STATE_BUSY, 15 ), 1987 ASSERT_ENUM_STRING( STATE_LOADING, 16 ), 1988 ASSERT_ENUM_STRING( STATE_INGAME, 17 ) 1989 }; 1990 return stateToString[ localState ]; 1991 } 1992 1993 // idSession interface 1994 1995 /* 1996 ======================== 1997 idSessionLocal::LoadingFinished 1998 1999 Only called by idCommonLocal::FinalizeMapChange 2000 ======================== 2001 */ 2002 void idSessionLocal::LoadingFinished() { 2003 NET_VERBOSE_PRINT( "NET: Loading Finished\n" ); 2004 2005 assert( GetState() == idSession::LOADING ); 2006 2007 common->Dialog().ClearDialog( GDM_VOICE_RESTRICTED ); 2008 GetActingGameStateLobby().loaded = true; 2009 2010 if ( MatchTypeIsLocal( GetActingGameStateLobby().parms.matchFlags ) ) { 2011 SetState( STATE_INGAME ); 2012 } else if ( !GetActingGameStateLobby().IsHost() ) { // Tell game host we're done loading 2013 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; 2014 idBitMsg msg( buffer, sizeof( buffer ) ); 2015 GetActingGameStateLobby().QueueReliableMessage( GetActingGameStateLobby().host, idLobby::RELIABLE_LOADING_DONE, msg.GetReadData(), msg.GetSize() ); 2016 } else { 2017 SetState( STATE_INGAME ); 2018 } 2019 2020 SetFlushedStats( false ); 2021 } 2022 2023 /* 2024 ======================== 2025 idSessionLocal::SendUsercmds 2026 ======================== 2027 */ 2028 void idSessionLocal::SendUsercmds( idBitMsg & msg ) { 2029 if ( localState != STATE_INGAME ) { 2030 return; 2031 } 2032 2033 if ( GetActingGameStateLobby().IsPeer() ) { 2034 idLobby::peer_t & hostPeer = GetActingGameStateLobby().peers[GetActingGameStateLobby().host]; 2035 2036 // Don't send user cmds if we have unsent packet fragments 2037 // (This can happen if we have packets to send, but SendAnotherFragment got throttled) 2038 if ( hostPeer.packetProc->HasMoreFragments() ) { 2039 idLib::Warning( "NET: Client called SendUsercmds while HasMoreFragments(). Skipping userCmds for this frame." ); 2040 return; 2041 } 2042 2043 int sequence = hostPeer.snapProc->GetLastAppendedSequence(); 2044 2045 // Add incoming BPS for QoS 2046 float incomingBPS = hostPeer.receivedBps; 2047 if ( hostPeer.receivedBpsIndex != sequence ) { 2048 incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), hostPeer.packetProc->GetIncomingRateBytes() ); 2049 hostPeer.receivedBpsIndex = sequence; 2050 hostPeer.receivedBps = incomingBPS; 2051 } 2052 uint16 incomingBPS_quantized = idMath::Ftoi( incomingBPS * ( ( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) / idLobby::BANDWIDTH_REPORTING_MAX ) ); 2053 2054 byte buffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE]; 2055 lzwCompressionData_t lzwData; 2056 idLZWCompressor lzwCompressor( &lzwData ); 2057 lzwCompressor.Start( buffer, sizeof( buffer ) ); 2058 lzwCompressor.WriteAgnostic( sequence ); 2059 lzwCompressor.WriteAgnostic( incomingBPS_quantized ); 2060 lzwCompressor.Write( msg.GetReadData(), msg.GetSize() ); 2061 lzwCompressor.End(); 2062 2063 GetActingGameStateLobby().ProcessOutgoingMsg( GetActingGameStateLobby().host, buffer, lzwCompressor.Length(), false, 0 ); 2064 2065 if ( net_debugBaseStates.GetBool() && sequence < 50 ) { 2066 idLib::Printf( "NET: Acking snap %d \n", sequence ); 2067 } 2068 } 2069 } 2070 2071 /* 2072 ======================== 2073 idSessionLocal::SendSnapshot 2074 ======================== 2075 */ 2076 void idSessionLocal::SendSnapshot( idSnapShot & ss ) { 2077 for ( int p = 0; p < GetActingGameStateLobby().peers.Num(); p++ ) { 2078 idLobby::peer_t & peer = GetActingGameStateLobby().peers[p]; 2079 2080 if ( !peer.IsConnected() ) { 2081 continue; 2082 } 2083 2084 if ( !peer.loaded ) { 2085 continue; 2086 } 2087 2088 if ( peer.pauseSnapshots ) { 2089 continue; 2090 } 2091 2092 GetActingGameStateLobby().SendSnapshotToPeer( ss, p ); 2093 } 2094 } 2095 2096 /* 2097 ======================== 2098 idSessionLocal::UpdateSignInManager 2099 ======================== 2100 */ 2101 void idSessionLocal::UpdateSignInManager() { 2102 if ( !HasSignInManager() ) { 2103 return; 2104 } 2105 2106 if ( net_headlessServer.GetBool() ) { 2107 return; 2108 } 2109 2110 // FIXME: We need to ask the menu system for this info. Just making a best guess for now 2111 // (assume we are allowed to join the party as a splitscreen user if we are in the party lobby) 2112 bool allowJoinParty = ( localState == STATE_PARTY_LOBBY_HOST || localState == STATE_PARTY_LOBBY_PEER ) && GetPartyLobby().state == idLobby::STATE_IDLE; 2113 bool allowJoinGame = ( localState == STATE_GAME_LOBBY_HOST || localState == STATE_GAME_LOBBY_PEER ) && GetGameLobby().state == idLobby::STATE_IDLE; 2114 2115 bool eitherLobbyRunning = GetActivePlatformLobby() != NULL && ( GetPartyLobby().IsLobbyActive() || GetGameLobby().IsLobbyActive() ); 2116 bool onlineMatch = eitherLobbyRunning && MatchTypeIsOnline( GetActivePlatformLobby()->parms.matchFlags ); 2117 2118 //================================================================================= 2119 // Get the number of desired signed in local users depending on what mode we're in. 2120 //================================================================================= 2121 int minDesiredUsers = 0; 2122 int maxDesiredUsers = Max( 1, signInManager->GetNumLocalUsers() ); 2123 2124 if ( si_splitscreen.GetInteger() != 0 ) { 2125 // For debugging, force 2 splitscreen players 2126 minDesiredUsers = 2; 2127 maxDesiredUsers = 2; 2128 allowJoinGame = true; 2129 } else if ( onlineMatch || ( eitherLobbyRunning == false ) ) { 2130 // If this an online game, then only 1 user can join locally. 2131 // Also, if no sessions are active, remove any extra players. 2132 maxDesiredUsers = 1; 2133 } else if ( allowJoinParty || allowJoinGame ) { 2134 // If we are in the party lobby, allow 2 splitscreen users to join 2135 maxDesiredUsers = 2; 2136 } 2137 2138 // Set the number of desired users 2139 signInManager->SetDesiredLocalUsers( minDesiredUsers, maxDesiredUsers ); 2140 2141 //================================================================================= 2142 // Update signin manager 2143 //================================================================================= 2144 2145 // Update signin mgr. This manager tracks signed in local users, which the session then uses 2146 // to determine who should be in the lobby. 2147 signInManager->Pump(); 2148 2149 // Get the master local user 2150 idLocalUser * masterUser = signInManager->GetMasterLocalUser(); 2151 2152 if ( onlineMatch && masterUser != NULL && !masterUser->CanPlayOnline() && !masterUser->HasOwnerChanged() ) { 2153 if ( localState > STATE_IDLE ) { 2154 // User is still valid, just no longer online 2155 if ( offlineTransitionTimerStart == 0 ) { 2156 offlineTransitionTimerStart = Sys_Milliseconds(); 2157 } 2158 2159 if ( ( Sys_Milliseconds() - offlineTransitionTimerStart ) > net_offlineTransitionThreshold.GetInteger() ) { 2160 MoveToMainMenu(); 2161 common->Dialog().ClearDialogs(); 2162 common->Dialog().AddDialog( GDM_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); 2163 } 2164 } 2165 return; // Bail out so signInManager->ValidateLocalUsers below doesn't prematurely remove the master user before we can detect loss of connection 2166 } else { 2167 offlineTransitionTimerStart = 0; 2168 } 2169 2170 // Remove local users (from the signin manager) who aren't allowed to be online if this is an online match. 2171 // Remove local user (from the signin manager) who are not properly signed into a profile. 2172 signInManager->ValidateLocalUsers( onlineMatch ); 2173 2174 //================================================================================= 2175 // Check to see if we need to go to "Press Start" 2176 //================================================================================= 2177 2178 // Get the master local user (again, after ValidateOnlineLocalUsers, to make sure he is still valid) 2179 masterUser = signInManager->GetMasterLocalUser(); 2180 2181 if ( masterUser == NULL ) { 2182 // If we don't have a master user at all, then we need to be at "Press Start" 2183 MoveToPressStart( GDM_SP_SIGNIN_CHANGE_POST ); 2184 return; 2185 } else if ( localState == STATE_PRESS_START ) { 2186 2187 2188 // If we have a master user, and we are at press start, move to the menu area 2189 SetState( STATE_IDLE ); 2190 2191 } 2192 2193 // See if the master user either isn't persistent (but needs to be), OR, if the owner changed 2194 // RequirePersistentMaster is poorly named, this really means RequireSignedInMaster 2195 if ( masterUser->HasOwnerChanged() || ( RequirePersistentMaster() && !masterUser->IsProfileReady() ) ) { 2196 MoveToPressStart( GDM_SP_SIGNIN_CHANGE_POST ); 2197 return; 2198 } 2199 2200 //================================================================================= 2201 // Sync lobby users with the signed in users 2202 // The initial list of session users are normally determined at connect or create time. 2203 // These functions allow splitscreen users to join in, or check to see if existing 2204 // users (including the master) need to be removed. 2205 //================================================================================= 2206 GetPartyLobby().SyncLobbyUsersWithLocalUsers( allowJoinParty, onlineMatch ); 2207 GetGameLobby().SyncLobbyUsersWithLocalUsers( allowJoinGame, onlineMatch ); 2208 GetGameStateLobby().SyncLobbyUsersWithLocalUsers( allowJoinGame, onlineMatch ); 2209 } 2210 2211 /* 2212 ======================== 2213 idSessionLocal::GetProfileFromMasterLocalUser 2214 ======================== 2215 */ 2216 idPlayerProfile * idSessionLocal::GetProfileFromMasterLocalUser() { 2217 idPlayerProfile * profile = NULL; 2218 idLocalUser * masterUser = signInManager->GetMasterLocalUser(); 2219 2220 if ( masterUser != NULL ) { 2221 profile = masterUser->GetProfile(); 2222 } 2223 2224 if ( profile == NULL ) { 2225 // Whoops 2226 profile = signInManager->GetDefaultProfile(); 2227 //idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." ); 2228 } 2229 2230 return profile; 2231 } 2232 2233 /* 2234 ======================== 2235 /* 2236 ======================== 2237 idSessionLocal::MoveToPressStart 2238 ======================== 2239 */ 2240 void idSessionLocal::MoveToPressStart( gameDialogMessages_t msg ) { 2241 if ( localState != STATE_PRESS_START ) { 2242 MoveToPressStart(); 2243 common->Dialog().ClearDialogs(); 2244 common->Dialog().AddDialog( msg, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); 2245 } 2246 } 2247 2248 /* 2249 ======================== 2250 idSessionLocal::GetPeerName 2251 ======================== 2252 */ 2253 const char * idSessionLocal::GetPeerName( int peerNum ) { 2254 return GetActingGameStateLobby().GetPeerName( peerNum ); 2255 } 2256 2257 2258 /* 2259 ======================== 2260 idSessionLocal::SetState 2261 ======================== 2262 */ 2263 void idSessionLocal::SetState( state_t newState ) { 2264 2265 assert( newState < NUM_STATES ); 2266 assert( localState < NUM_STATES ); 2267 verify_array_size( stateToString, NUM_STATES ); 2268 2269 if ( newState == localState ) { 2270 NET_VERBOSE_PRINT( "NET: SetState: State SAME %s\n", stateToString[ newState ] ); 2271 return; 2272 } 2273 2274 // Set the current state 2275 NET_VERBOSE_PRINT( "NET: SetState: State changing from %s to %s\n", stateToString[ localState ], stateToString[ newState ] ); 2276 2277 if ( localState < STATE_LOADING && newState >= STATE_LOADING ) { 2278 // Tell lobby instances that the match has started 2279 StartSessions(); 2280 // Clear certain dialog boxes we don't want to see in-game 2281 common->Dialog().ClearDialog( GDM_LOBBY_DISBANDED ); // The lobby you were previously in has disbanded 2282 } else if ( localState >= STATE_LOADING && newState < STATE_LOADING ) { 2283 // Tell lobby instances that the match has ended 2284 if ( !WasMigrationGame() ) { // Don't end the session if we are going right back into the game 2285 EndSessions(); 2286 } 2287 } 2288 2289 if ( newState == STATE_GAME_LOBBY_HOST || newState == STATE_GAME_LOBBY_PEER ) { 2290 ComputeNextGameCoalesceTime(); 2291 } 2292 2293 localState = newState; 2294 } 2295 2296 /* 2297 ======================== 2298 idSessionLocal::HandlePackets 2299 ======================== 2300 */ 2301 bool idSessionLocal::HandlePackets() { 2302 SCOPED_PROFILE_EVENT( "Session::HandlePackets" ); 2303 2304 byte packetBuffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ]; 2305 lobbyAddress_t remoteAddress; 2306 int recvSize = 0; 2307 bool fromDedicated = false; 2308 2309 while ( ReadRawPacket( remoteAddress, packetBuffer, recvSize, fromDedicated, sizeof( packetBuffer ) ) && recvSize > 0 ) { 2310 2311 // fragMsg will hold the raw packet 2312 idBitMsg fragMsg; 2313 fragMsg.InitRead( packetBuffer, recvSize ); 2314 2315 // Peek at the session ID 2316 idPacketProcessor::sessionId_t sessionID = idPacketProcessor::GetSessionID( fragMsg ); 2317 2318 // idLib::Printf( "NET: HandlePackets - session %d, size %d \n", sessionID, recvSize ); 2319 2320 // Make sure it's valid 2321 if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) { 2322 idLib::Printf( "NET: Invalid sessionID %s.\n", remoteAddress.ToString() ); 2323 continue; 2324 } 2325 2326 // 2327 // Distribute the packet to the proper lobby 2328 // 2329 2330 const int maskedType = sessionID & idPacketProcessor::LOBBY_TYPE_MASK; 2331 2332 if ( !verify( maskedType > 0 ) ) { 2333 continue; 2334 } 2335 2336 idLobby::lobbyType_t lobbyType = (idLobby::lobbyType_t)( maskedType - 1 ); 2337 2338 switch ( lobbyType ) { 2339 case idLobby::TYPE_PARTY: GetPartyLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); break; 2340 case idLobby::TYPE_GAME: GetGameLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); break; 2341 case idLobby::TYPE_GAME_STATE: GetGameStateLobby().HandlePacket( remoteAddress, fragMsg, sessionID ); break; 2342 default: assert( 0 ); 2343 } 2344 } 2345 2346 return false; 2347 } 2348 2349 /* 2350 ======================== 2351 idSessionLocal::GetActivePlatformLobby 2352 ======================== 2353 */ 2354 idLobby * idSessionLocal::GetActivePlatformLobby() { 2355 sessionState_t state = GetState(); 2356 2357 if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) { 2358 return &GetGameLobby(); 2359 } else if ( state == PARTY_LOBBY ) { 2360 return &GetPartyLobby(); 2361 } 2362 2363 return NULL; 2364 } 2365 2366 /* 2367 ======================== 2368 idSessionLocal::GetActivePlatformLobby 2369 ======================== 2370 */ 2371 const idLobby * idSessionLocal::GetActivePlatformLobby() const { 2372 sessionState_t state = GetState(); 2373 2374 if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) { 2375 return &GetGameLobby(); 2376 } else if ( state == PARTY_LOBBY ) { 2377 return &GetPartyLobby(); 2378 } 2379 2380 return NULL; 2381 } 2382 2383 /* 2384 ======================== 2385 idSessionLocal::GetActingGameStateLobby 2386 ======================== 2387 */ 2388 idLobby & idSessionLocal::GetActingGameStateLobby() { 2389 if ( net_useGameStateLobby.GetBool() ) { 2390 return GetGameStateLobby(); 2391 } 2392 2393 return GetGameLobby(); 2394 } 2395 2396 /* 2397 ======================== 2398 idSessionLocal::GetActingGameStateLobby 2399 ======================== 2400 */ 2401 const idLobby & idSessionLocal::GetActingGameStateLobby() const { 2402 if ( net_useGameStateLobby.GetBool() ) { 2403 return GetGameStateLobby(); 2404 } 2405 2406 return GetGameLobby(); 2407 } 2408 2409 /* 2410 ======================== 2411 idSessionLocal::GetLobbyFromType 2412 ======================== 2413 */ 2414 idLobby * idSessionLocal::GetLobbyFromType( idLobby::lobbyType_t lobbyType ) { 2415 switch ( lobbyType ) { 2416 case idLobby::TYPE_PARTY: return &GetPartyLobby(); 2417 case idLobby::TYPE_GAME: return &GetGameLobby(); 2418 case idLobby::TYPE_GAME_STATE: return &GetGameStateLobby(); 2419 } 2420 2421 return NULL; 2422 } 2423 2424 /* 2425 ======================== 2426 idSessionLocal::GetActivePlatformLobbyBase 2427 This returns the base version for the idSession version 2428 ======================== 2429 */ 2430 idLobbyBase & idSessionLocal::GetActivePlatformLobbyBase() { 2431 idLobby * activeLobby = GetActivePlatformLobby(); 2432 2433 if ( activeLobby != NULL ) { 2434 return *activeLobby; 2435 } 2436 2437 return stubLobby; // So we can return at least something 2438 } 2439 2440 /* 2441 ======================== 2442 idSessionLocal::GetLobbyFromLobbyUserID 2443 ======================== 2444 */ 2445 idLobbyBase & idSessionLocal::GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID ) { 2446 if ( !lobbyUserID.IsValid() ) { 2447 return stubLobby; // So we can return at least something 2448 } 2449 2450 idLobby * lobby = GetLobbyFromType( (idLobby::lobbyType_t)lobbyUserID.GetLobbyType() ); 2451 2452 if ( lobby != NULL ) { 2453 return *lobby; 2454 } 2455 2456 return stubLobby; // So we can return at least something 2457 } 2458 2459 /* 2460 ======================== 2461 idSessionLocal::TickSendQueue 2462 ======================== 2463 */ 2464 void idSessionLocal::TickSendQueue() { 2465 assert( !sendQueue.IsEmpty() ); 2466 int now = Sys_Milliseconds(); 2467 idQueuePacket * packet = sendQueue.Peek(); 2468 while ( packet != NULL ) { 2469 if ( now < packet->time ) { 2470 break; 2471 } 2472 2473 GetPort( packet->dedicated ).SendRawPacket( packet->address, packet->data, packet->size ); 2474 2475 if ( net_forceUpstream.GetFloat() != 0.0f && net_forceUpstreamQueue.GetFloat() != 0.0f ) { 2476 // FIXME: no can do both 2477 assert( net_forceLatency.GetInteger() == 0 ); 2478 // compute / update an added traffic due to the queuing 2479 // we can't piggyback on upstreamDropRate because of the way it's computed and clamped to zero 2480 int time = Sys_Milliseconds(); 2481 if ( time > upstreamQueueRateTime ) { 2482 upstreamQueueRate -= upstreamQueueRate * ( float )( time - upstreamQueueRateTime ) / 1000.0f; 2483 if ( upstreamQueueRate < 0.0f ) { 2484 upstreamQueueRate = 0.0f; 2485 } 2486 upstreamQueueRateTime = time; 2487 } 2488 // update queued bytes 2489 queuedBytes -= packet->size; 2490 if ( net_verboseSimulatedTraffic.GetBool() ) { 2491 idLib::Printf( "send queued packet size %d to %s\n", packet->size, packet->address.ToString() ); 2492 } 2493 } 2494 2495 sendQueue.RemoveFirst(); // we have it already, just push it off the queue before freeing 2496 packetAllocator.Free( packet ); 2497 packet = sendQueue.Peek(); 2498 } 2499 } 2500 2501 /* 2502 ======================== 2503 idSessionLocal::QueuePacket 2504 ======================== 2505 */ 2506 void idSessionLocal::QueuePacket( idQueue< idQueuePacket,&idQueuePacket::queueNode > & queue, int time, const lobbyAddress_t & to, const void * data, int size, bool dedicated ) { 2507 //mem.PushHeap(); 2508 2509 idQueuePacket * packet = packetAllocator.Alloc(); 2510 2511 packet->address = to; 2512 packet->size = size; 2513 packet->dedicated = dedicated; 2514 packet->time = time; 2515 2516 memcpy( packet->data, data, size ); 2517 2518 queue.Add( packet ); 2519 2520 //mem.PopHeap(); 2521 } 2522 2523 /* 2524 ======================== 2525 idSessionLocal::ReadRawPacketFromQueue 2526 ======================== 2527 */ 2528 bool idSessionLocal::ReadRawPacketFromQueue( int time, lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ) { 2529 idQueuePacket * packet = recvQueue.Peek(); 2530 2531 if ( packet == NULL || time < packet->time ) { 2532 return false; // Either there are no packets, or no packet is ready 2533 } 2534 2535 //idLib::Printf( "NET: Packet recvd: %d ms\n", now ); 2536 2537 from = packet->address; 2538 size = packet->size; 2539 assert( size <= maxSize ); 2540 outDedicated = packet->dedicated; 2541 memcpy( data, packet->data, packet->size ); 2542 recvQueue.RemoveFirst(); // we have it already, just push it off the queue before freeing 2543 packetAllocator.Free( packet ); 2544 2545 return true; 2546 } 2547 2548 /* 2549 ======================== 2550 idSessionLocal::SendRawPacket 2551 ======================== 2552 */ 2553 void idSessionLocal::SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool dedicated ) { 2554 const int now = Sys_Milliseconds(); 2555 2556 if ( net_forceUpstream.GetFloat() != 0 ) { 2557 2558 // the total bandwidth rate at which the networking systems are trying to push data through 2559 float totalOutgoingRate = (float)GetActingGameStateLobby().GetTotalOutgoingRate(); // B/s 2560 2561 // update the rate at which we have been taking data out by dropping it 2562 int time = Sys_Milliseconds(); 2563 if ( time > upstreamDropRateTime ) { 2564 upstreamDropRate -= upstreamDropRate * ( float )( time - upstreamDropRateTime ) / 1000.0f; 2565 if ( upstreamDropRate < 0.0f ) { 2566 upstreamDropRate = 0.0f; 2567 } 2568 upstreamDropRateTime = time; 2569 } 2570 2571 if ( (float)( totalOutgoingRate - upstreamDropRate + upstreamQueueRate ) > net_forceUpstream.GetFloat() * 1024.0f ) { // net_forceUpstream is in kB/s, everything else in B/s 2572 if ( net_forceUpstreamQueue.GetFloat() == 0.0f ) { 2573 // just drop the packet - not representative, but simple 2574 if ( net_verboseSimulatedTraffic.GetBool() ) { 2575 idLib::Printf( "drop %d bytes to %s\n", size, to.ToString() ); 2576 } 2577 // increase the instant drop rate with the data we just dropped 2578 upstreamDropRate += size; 2579 return; 2580 } 2581 2582 // simulate a network device with a send queue 2583 // do we have room in the queue? 2584 assert( net_forceUpstreamQueue.GetFloat() > 0.0f ); 2585 if ( (float)( queuedBytes + size ) > net_forceUpstreamQueue.GetFloat() * 1024.0f ) { // net_forceUpstreamQueue is in kB/s 2586 // too much queued, this is still a drop 2587 // FIXME: factorize 2588 // just drop the packet - not representative, but simple 2589 if ( net_verboseSimulatedTraffic.GetBool() ) { 2590 idLib::Printf( "full queue: drop %d bytes to %s\n", size, to.ToString() ); 2591 } 2592 // increase the instant drop rate with the data we just dropped 2593 upstreamDropRate += size; 2594 return; 2595 } 2596 // there is room to buffer up in the queue 2597 queuedBytes += size; 2598 // with queuedBytes and the current upstream, when should this packet be sent? 2599 int queuedPacketSendDelay = 1000.0f * ( (float)queuedBytes / ( net_forceUpstream.GetFloat() * 1024.0f ) ); // in ms 2600 // queue for sending 2601 if ( net_verboseSimulatedTraffic.GetBool() ) { 2602 idLib::Printf( "queuing packet: %d bytes delayed %d ms\n", size, queuedPacketSendDelay ); 2603 } 2604 2605 QueuePacket( sendQueue, now + queuedPacketSendDelay, to, data, size, dedicated ); 2606 2607 // will abuse the forced latency code below to take care of the sending 2608 // FIXME: right now, can't have both on 2609 assert( net_forceLatency.GetInteger() == 0 ); 2610 } 2611 } 2612 2613 // short path 2614 // NOTE: network queuing: will go to tick the queue whenever sendQueue isn't empty, regardless of latency 2615 if ( net_forceLatency.GetInteger() == 0 && sendQueue.IsEmpty() ) { 2616 GetPort( dedicated ).SendRawPacket( to, data, size ); 2617 return; 2618 } 2619 2620 if ( net_forceUpstream.GetFloat() != 0.0f && net_forceUpstreamQueue.GetFloat() != 0.0f ) { 2621 // FIXME: not doing both just yet 2622 assert( net_forceLatency.GetInteger() == 0 ); 2623 TickSendQueue(); 2624 return; // we done (at least for queue only path) 2625 } 2626 2627 // queue up 2628 assert( size != 0 && size <= idPacketProcessor::MAX_FINAL_PACKET_SIZE ); 2629 2630 QueuePacket( sendQueue, now + net_forceLatency.GetInteger() / 2, to, data, size, dedicated ); 2631 2632 TickSendQueue(); 2633 } 2634 2635 /* 2636 ======================== 2637 idSessionLocal::ReadRawPacket 2638 ======================== 2639 */ 2640 bool idSessionLocal::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize ) { 2641 SCOPED_PROFILE_EVENT( "Session::ReadRawPacket" ); 2642 2643 assert( maxSize <= idPacketProcessor::MAX_FINAL_PACKET_SIZE ); 2644 2645 if ( !sendQueue.IsEmpty() ) { 2646 TickSendQueue(); 2647 } 2648 2649 const int now = Sys_Milliseconds(); 2650 2651 // Make sure we give both ports equal time 2652 static bool currentDedicated = false; 2653 currentDedicated = !currentDedicated; 2654 2655 for ( int i = 0; i < 2; i++ ) { 2656 // BRIAN_FIXME: Dedicated servers fuck up running 2 instances on the same machine 2657 // outDedicated = ( i == 0 ) ? currentDedicated : !currentDedicated; 2658 outDedicated = false; 2659 2660 if ( GetPort( outDedicated ).ReadRawPacket( from, data, size, maxSize ) ) { 2661 if ( net_forceLatency.GetInteger() == 0 && recvQueue.IsEmpty() ) { 2662 // If we aren't forcing latency, and queue is empty, return result immediately 2663 return true; 2664 } 2665 2666 // the cvar is meant to be a round trip latency so we're applying half on the send and half on the recv 2667 const int time = ( net_forceLatency.GetInteger() == 0 ) ? 0 : now + net_forceLatency.GetInteger() / 2; 2668 2669 // Otherwise, queue result 2670 QueuePacket( recvQueue, time, from, data, size, outDedicated ); 2671 } 2672 } 2673 2674 // Return any queued results 2675 return ReadRawPacketFromQueue( now, from, data, size, outDedicated, maxSize ); 2676 } 2677 2678 /* 2679 ======================== 2680 idSessionLocal::ConnectAndMoveToLobby 2681 ======================== 2682 */ 2683 void idSessionLocal::ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ) { 2684 2685 // Since we are connecting directly to a lobby, make sure no search results are left over from previous FindOrCreateMatch results 2686 // If we don't do this, we might think we should attempt to connect to an old search result, and we don't want to in this case 2687 lobby.searchResults.Clear(); 2688 2689 // Attempt to connect to the lobby 2690 lobby.ConnectTo( connectInfo, fromInvite ); 2691 2692 connectType = CONNECT_DIRECT; 2693 2694 // Wait for connection 2695 switch ( lobby.lobbyType ) { 2696 case idLobby::TYPE_PARTY: SetState( STATE_CONNECT_AND_MOVE_TO_PARTY ); break; 2697 case idLobby::TYPE_GAME: SetState( STATE_CONNECT_AND_MOVE_TO_GAME ); break; 2698 case idLobby::TYPE_GAME_STATE: SetState( STATE_CONNECT_AND_MOVE_TO_GAME_STATE ); break; 2699 } 2700 } 2701 2702 /* 2703 ======================== 2704 idSessionLocal::GoodbyeFromHost 2705 ======================== 2706 */ 2707 void idSessionLocal::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) { 2708 if ( !verify( localState > STATE_IDLE ) ) { 2709 idLib::Printf( "NET: Got disconnected from host %s on session %s when we were not in a lobby or game.\n", remoteAddress.ToString(), lobby.GetLobbyName() ); 2710 MoveToMainMenu(); 2711 return; // Ignore if we are not past the main menu 2712 } 2713 2714 // Goodbye from host. See if we were connecting vs connected 2715 if ( ( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ) && lobby.peers[peerNum].GetConnectionState() == idLobby::CONNECTION_CONNECTING ) { 2716 // We were denied a connection attempt 2717 idLib::Printf( "NET: Denied connection attempt from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType ); 2718 // This will try to move to the next connection if one exists, otherwise will create a match 2719 HandleConnectionFailed( lobby, msgType == idLobby::OOB_GOODBYE_FULL ); 2720 } else { 2721 // We were disconnected from a server we were previously connected to 2722 idLib::Printf( "NET: Disconnected from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType ); 2723 2724 const bool leaveGameWithParty = ( msgType == idLobby::OOB_GOODBYE_W_PARTY ); 2725 2726 if ( leaveGameWithParty && lobby.lobbyType == idLobby::TYPE_GAME && lobby.IsPeer() && GetState() == idSession::GAME_LOBBY && GetPartyLobby().host >= 0 && 2727 lobby.peers[peerNum].address.Compare( GetPartyLobby().peers[GetPartyLobby().host].address, true ) ) { 2728 // If a host is telling us goodbye from a game lobby, and the game host is the same as our party host, 2729 // and we aren't in a game, and the host wants us to leave with him, then do so now 2730 GetGameLobby().Shutdown(); 2731 GetGameStateLobby().Shutdown(); 2732 SetState( STATE_PARTY_LOBBY_PEER ); 2733 } else { 2734 // Host left, so pick a new host (possibly even us) for this lobby 2735 lobby.PickNewHost(); 2736 } 2737 } 2738 } 2739 2740 /* 2741 ======================== 2742 idSessionLocal::WriteLeaderboardToMsg 2743 ======================== 2744 */ 2745 void idSessionLocal::WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ) { 2746 assert( Sys_FindLeaderboardDef( leaderboard->id ) == leaderboard ); 2747 2748 msg.WriteLong( leaderboard->id ); 2749 2750 for ( int i = 0; i < leaderboard->numColumns; ++i ) { 2751 uint64 value = stats[i].value; 2752 2753 //idLib::Printf( "value = %i\n", (int32)value ); 2754 2755 for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) { 2756 msg.WriteBits( value & 1, 1 ); 2757 value >>= 1; 2758 } 2759 //msg.WriteData( &stats[i].value, sizeof( stats[i].value ) ); 2760 } 2761 } 2762 2763 /* 2764 ======================== 2765 idSessionLocal::ReadLeaderboardFromMsg 2766 ======================== 2767 */ 2768 const leaderboardDefinition_t * idSessionLocal::ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ) { 2769 int id = msg.ReadLong(); 2770 2771 const leaderboardDefinition_t * leaderboard = Sys_FindLeaderboardDef( id ); 2772 2773 if ( leaderboard == NULL ) { 2774 idLib::Printf( "NET: Invalid leaderboard id: %i\n", id ); 2775 return NULL; 2776 } 2777 2778 for ( int i = 0; i < leaderboard->numColumns; ++i ) { 2779 uint64 value = 0; 2780 2781 for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) { 2782 value |= (uint64)( msg.ReadBits( 1 ) & 1 ) << j; 2783 } 2784 2785 stats[i].value = value; 2786 2787 //idLib::Printf( "value = %i\n", (int32)value ); 2788 //msg.ReadData( &stats[i].value, sizeof( stats[i].value ) ); 2789 } 2790 2791 return leaderboard; 2792 } 2793 2794 /* 2795 ======================== 2796 idSessionLocal::SendLeaderboardStatsToPlayer 2797 ======================== 2798 */ 2799 void idSessionLocal::SendLeaderboardStatsToPlayer( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats ) { 2800 2801 const int sessionUserIndex = GetActingGameStateLobby().GetLobbyUserIndexByID( lobbyUserID ); 2802 2803 if ( GetActingGameStateLobby().IsLobbyUserDisconnected( sessionUserIndex ) ) { 2804 idLib::Warning( "Tried to tell disconnected user to report stats" ); 2805 return; 2806 } 2807 2808 const int peerIndex = GetActingGameStateLobby().PeerIndexFromLobbyUser( lobbyUserID ); 2809 2810 if ( peerIndex == -1 ) { 2811 idLib::Warning( "Tried to tell invalid peer index to report stats" ); 2812 return; 2813 } 2814 2815 if ( !verify( GetActingGameStateLobby().IsHost() ) || 2816 !verify( peerIndex < GetActingGameStateLobby().peers.Num() ) || 2817 !verify( GetActingGameStateLobby().peers[ peerIndex ].IsConnected() ) ) { 2818 idLib::Warning( "Tried to tell invalid peer to report stats" ); 2819 return; 2820 } 2821 2822 NET_VERBOSE_PRINT( "Telling sessionUserIndex %i (peer %i) to report stats\n", sessionUserIndex, peerIndex ); 2823 2824 lobbyUser_t * gameUser = GetActingGameStateLobby().GetLobbyUser( sessionUserIndex ); 2825 2826 if ( !verify( gameUser != NULL ) ) { 2827 return; 2828 } 2829 2830 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ]; 2831 idBitMsg msg( buffer, sizeof( buffer ) ); 2832 2833 // Use the user ID 2834 gameUser->lobbyUserID.WriteToMsg( msg ); 2835 2836 WriteLeaderboardToMsg( msg, leaderboard, stats ); 2837 2838 GetActingGameStateLobby().QueueReliableMessage( peerIndex, idLobby::RELIABLE_POST_STATS, msg.GetReadData(), msg.GetSize() ); 2839 } 2840 2841 /* 2842 ======================== 2843 idSessionLocal::RecvLeaderboardStatsForPlayer 2844 ======================== 2845 */ 2846 void idSessionLocal::RecvLeaderboardStatsForPlayer( idBitMsg & msg ) { 2847 column_t stats[ MAX_LEADERBOARD_COLUMNS ]; 2848 2849 lobbyUserID_t lobbyUserID; 2850 lobbyUserID.ReadFromMsg( msg ); 2851 2852 const leaderboardDefinition_t * leaderboard = ReadLeaderboardFromMsg( msg, stats ); 2853 2854 if ( leaderboard == NULL ) { 2855 idLib::Printf( "RecvLeaderboardStatsForPlayer: Invalid lb.\n" ); 2856 return; 2857 } 2858 2859 LeaderboardUpload( lobbyUserID, leaderboard, stats ); 2860 } 2861 2862 /* 2863 ======================== 2864 idSessionLocal::RequirePersistentMaster 2865 ======================== 2866 */ 2867 bool idSessionLocal::RequirePersistentMaster() { 2868 return signInManager->RequirePersistentMaster(); 2869 } 2870 2871 /* 2872 ======================== 2873 CheckAndUpdateValue 2874 ======================== 2875 */ 2876 template<typename T> 2877 bool CheckAndUpdateValue( T & value, const T & newValue ) { 2878 if ( value == newValue ) { 2879 return false; 2880 } 2881 value = newValue; 2882 return true; 2883 } 2884 2885 /* 2886 ======================== 2887 lobbyUser_t::UpdateClientMutableData 2888 ======================== 2889 */ 2890 bool lobbyUser_t::UpdateClientMutableData( const idLocalUser * localUser ) { 2891 bool updated = false; 2892 const idPlayerProfile * profile = localUser->GetProfile(); 2893 if ( profile != NULL ) { 2894 updated |= CheckAndUpdateValue( level, profile->GetLevel() ); 2895 } 2896 updated |= CheckAndUpdateValue( selectedSkin, ui_skinIndex.GetInteger() ); 2897 updated |= CheckAndUpdateValue( weaponAutoSwitch, ui_autoSwitch.GetBool() ); 2898 updated |= CheckAndUpdateValue( weaponAutoReload, ui_autoReload.GetBool() ); 2899 return updated; 2900 } 2901 2902 /* 2903 ======================== 2904 idSessionLocal::ComputeNextGameCoalesceTime 2905 ======================== 2906 */ 2907 void idSessionLocal::ComputeNextGameCoalesceTime() { 2908 const int coalesceTimeInSeconds = session->GetTitleStorageInt( "net_LobbyCoalesceTimeInSeconds", net_LobbyCoalesceTimeInSeconds.GetInteger() ); 2909 const int randomCoalesceTimeInSeconds = session->GetTitleStorageInt( "net_LobbyRandomCoalesceTimeInSeconds", net_LobbyRandomCoalesceTimeInSeconds.GetInteger() ); 2910 2911 if ( coalesceTimeInSeconds != 0 ) { 2912 static idRandom2 random( Sys_Milliseconds() ); 2913 2914 nextGameCoalesceTime = Sys_Milliseconds() + ( coalesceTimeInSeconds + random.RandomInt( randomCoalesceTimeInSeconds ) ) * 1000; 2915 } else { 2916 nextGameCoalesceTime = 0; 2917 } 2918 } 2919 2920 /* 2921 ======================== 2922 lobbyUser_t::Net_BandwidthChallenge 2923 ======================== 2924 */ 2925 CONSOLE_COMMAND( Net_BandwidthChallenge, "Test network bandwidth", 0 ) { 2926 session->StartOrContinueBandwidthChallenge( true ); 2927 } 2928 2929 /* 2930 ======================== 2931 lobbyUser_t::Net_ThrottlePeer 2932 ======================== 2933 */ 2934 CONSOLE_COMMAND( Net_ThrottlePeer, "Test network bandwidth", 0 ) { 2935 2936 int peerNum = -1; 2937 int snapRate = 0; 2938 2939 if ( args.Argc() >= 3 ) { 2940 peerNum = atoi( args.Argv(1) ); 2941 snapRate = atoi( args.Argv(2) ); 2942 } 2943 2944 // Note DebugSetPeerSnaprate will handle peerNum=-1 by printing out list of peers 2945 session->DebugSetPeerSnaprate( peerNum, snapRate ); 2946 } 2947 2948 2949 // FIXME: Move to sys_stats.cpp 2950 idStaticList< leaderboardDefinition_t *, MAX_LEADERBOARDS > registeredLeaderboards; 2951 2952 /* 2953 ======================== 2954 Sys_FindLeaderboardDef 2955 ======================== 2956 */ 2957 const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ) { 2958 for ( int i = 0; i < registeredLeaderboards.Num() ; i++ ) { 2959 if ( registeredLeaderboards[i] && registeredLeaderboards[i]->id == id ) { 2960 return registeredLeaderboards[i]; 2961 } 2962 } 2963 2964 return NULL; 2965 } 2966 2967 /* 2968 ======================== 2969 Sys_CreateLeaderboardDef 2970 ======================== 2971 */ 2972 leaderboardDefinition_t * Sys_CreateLeaderboardDef( int id_, int numColumns_, const columnDef_t * columnDefs_, 2973 rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) { 2974 2975 leaderboardDefinition_t * newDef = new (TAG_NETWORKING) leaderboardDefinition_t( id_, numColumns_, columnDefs_, rankOrder_, supportsAttachments_, checkAgainstCurrent_ ); 2976 2977 // try and reuse a free spot 2978 int leaderboardHandle = registeredLeaderboards.FindNull(); 2979 2980 if ( leaderboardHandle == -1 ) { 2981 leaderboardHandle = registeredLeaderboards.Append( NULL ); 2982 } 2983 2984 registeredLeaderboards[ leaderboardHandle ] = newDef; 2985 2986 return newDef; 2987 } 2988 2989 /* 2990 ======================== 2991 Sys_CreateLeaderboardDef 2992 ======================== 2993 */ 2994 void Sys_DestroyLeaderboardDefs() { 2995 2996 // delete and clear all the contents of the registeredLeaderboards static list. 2997 registeredLeaderboards.DeleteContents( true ); 2998 } 2999 3000 /* 3001 ======================== 3002 idSessionLocal::StartOrContinueBandwidthChallenge 3003 This will start a bandwidth test if one is not active 3004 returns true if a test has completed 3005 ======================== 3006 */ 3007 bool idSessionLocal::StartOrContinueBandwidthChallenge( bool forceStart ) { 3008 idLobby * activeLobby = GetActivePlatformLobby(); 3009 if ( activeLobby == NULL ) { 3010 idLib::Warning("No active session lobby when idSessionLocal::StartBandwidthChallenge called"); 3011 return true; 3012 } 3013 3014 if ( !forceStart && activeLobby->bandwidthChallengeFinished ) { 3015 activeLobby->bandwidthChallengeFinished = false; 3016 return true; 3017 } 3018 3019 if ( !activeLobby->BandwidthTestStarted() ) { 3020 activeLobby->BeginBandwidthTest(); 3021 } 3022 3023 return false; 3024 } 3025 3026 /* 3027 ======================== 3028 idSessionLocal::DebugSetPeerSnaprate 3029 This is debug function for manually setting peer's snaprate in game 3030 ======================== 3031 */ 3032 void idSessionLocal::DebugSetPeerSnaprate( int peerIndex, int snapRateMS ) { 3033 idLobby * activeLobby = GetActivePlatformLobby(); 3034 if ( activeLobby == NULL ) { 3035 idLib::Warning("No active session lobby when idSessionLocal::StartBandwidthChallenge called"); 3036 return; 3037 } 3038 3039 if ( peerIndex < 0 || peerIndex > activeLobby->peers.Num() ) { 3040 idLib::Printf("Invalid peer %d\n", peerIndex ); 3041 for ( int i=0; i < activeLobby->peers.Num(); i++ ) { 3042 idLib::Printf( "Peer[%d] %s\n", i, activeLobby->GetPeerName(i) ); 3043 } 3044 return; 3045 } 3046 3047 activeLobby->peers[peerIndex].throttledSnapRate = snapRateMS * 1000; 3048 activeLobby->peers[peerIndex].receivedThrottle = 0; 3049 idLib::Printf( "Set peer %s new snapRate: %d\n", activeLobby->GetPeerName(peerIndex), activeLobby->peers[peerIndex].throttledSnapRate ); 3050 } 3051 3052 /* 3053 ======================== 3054 idSessionLocal::DebugSetPeerSnaprate 3055 This is debug function for manually setting peer's snaprate in game 3056 ======================== 3057 */ 3058 float idSessionLocal::GetIncomingByteRate() { 3059 idLobby * activeLobby = GetActivePlatformLobby(); 3060 if ( activeLobby == NULL ) { 3061 idLib::Warning("No active session lobby when idSessionLocal::GetIncomingByteRate called"); 3062 return 0; 3063 } 3064 3065 float total = 0; 3066 for ( int p=0; p < activeLobby->peers.Num(); p++ ) { 3067 if ( activeLobby->peers[p].IsConnected() ) { 3068 total += activeLobby->peers[p].packetProc->GetIncomingRateBytes(); 3069 } 3070 } 3071 3072 return total; 3073 } 3074 3075 /* 3076 ======================== 3077 idSessionLocal::OnLocalUserSignin 3078 ======================== 3079 */ 3080 void idSessionLocal::OnLocalUserSignin( idLocalUser * user ) { 3081 // Do stuff before calling OnMasterLocalUserSignin() 3082 session->GetAchievementSystem().RegisterLocalUser( user ); 3083 3084 // We may not have a profile yet, need to call user's version... 3085 user->LoadProfileSettings(); 3086 3087 // for all consoles except the PS3 we enumerate right away because they don't 3088 // take such a long time as the PS3. PS3 enumeration is done in the 3089 // background and kicked off when the profile callback is triggered 3090 if ( user == GetSignInManager().GetMasterLocalUser() ) { 3091 OnMasterLocalUserSignin(); 3092 } 3093 } 3094 3095 /* 3096 ======================== 3097 idSessionLocal::OnLocalUserSignout 3098 ======================== 3099 */ 3100 void idSessionLocal::OnLocalUserSignout( idLocalUser * user ) { 3101 // Do stuff before calling OnMasterLocalUserSignout() 3102 session->GetAchievementSystem().RemoveLocalUser( user ); 3103 3104 if ( GetSignInManager().GetMasterLocalUser() == NULL ) { 3105 OnMasterLocalUserSignout(); 3106 } 3107 } 3108 3109 /* 3110 ======================== 3111 idSessionLocal::OnMasterLocalUserSignout 3112 ======================== 3113 */ 3114 void idSessionLocal::OnMasterLocalUserSignout() { 3115 CancelSaveGameWithHandle( enumerationHandle ); 3116 enumerationHandle = 0; 3117 GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear(); 3118 } 3119 3120 /* 3121 ======================== 3122 idSessionLocal::OnMasterLocalUserSignin 3123 ======================== 3124 */ 3125 void idSessionLocal::OnMasterLocalUserSignin() { 3126 enumerationHandle = EnumerateSaveGamesAsync(); 3127 } 3128 3129 /* 3130 ======================== 3131 idSessionLocal::OnLocalUserProfileLoaded 3132 ======================== 3133 */ 3134 void idSessionLocal::OnLocalUserProfileLoaded( idLocalUser * user ) { 3135 user->RequestSyncAchievements(); 3136 } 3137 3138 /* 3139 ======================== 3140 idSessionLocal::SetVoiceGroupsToTeams 3141 ======================== 3142 */ 3143 void idSessionLocal::SetVoiceGroupsToTeams() { 3144 // move voice chat to team 3145 int myTeam = 0; 3146 for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) { 3147 const lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( i ); 3148 3149 if ( !verify( gameUser != NULL ) ) { 3150 continue; 3151 } 3152 3153 if ( gameUser->IsDisconnected() ) { 3154 continue; 3155 } 3156 3157 int userTeam = gameUser->teamNumber; 3158 3159 voiceChat->SetTalkerGroup( gameUser, GetGameLobby().lobbyType, userTeam ); 3160 3161 if ( GetGameLobby().IsSessionUserIndexLocal( i ) ) { 3162 myTeam = userTeam; 3163 } 3164 } 3165 3166 SetActiveChatGroup( myTeam ); 3167 } 3168 3169 /* 3170 ======================== 3171 idSessionLocal::ClearVoiceGroups 3172 ======================== 3173 */ 3174 void idSessionLocal::ClearVoiceGroups() { 3175 for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) { 3176 const lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( i ); 3177 3178 if ( !verify( gameUser != NULL ) ) { 3179 continue; 3180 } 3181 3182 if ( gameUser->IsDisconnected() ) { 3183 continue; 3184 } 3185 3186 voiceChat->SetTalkerGroup( gameUser, GetGameLobby().lobbyType, 0 ); 3187 } 3188 3189 SetActiveChatGroup( 0 ); 3190 } 3191 3192 /* 3193 ======================== 3194 idSessionLocal::SendVoiceAudio 3195 ======================== 3196 */ 3197 void idSessionLocal::SendVoiceAudio() { 3198 if ( voiceChat == NULL ) { 3199 return; 3200 } 3201 3202 idLobby * activeLobby = GetActivePlatformLobby(); 3203 3204 int activeSessionIndex = ( activeLobby != NULL ) ? activeLobby->lobbyType : -1; 3205 3206 voiceChat->SetActiveLobby( activeSessionIndex ); 3207 voiceChat->Pump(); 3208 3209 if ( activeLobby == NULL ) { 3210 return; 3211 } 3212 3213 int time = Sys_Milliseconds(); 3214 3215 const int VOICE_THROTTLE_TIME_IN_MS = session->GetTitleStorageInt( "VOICE_THROTTLE_TIME_IN_MS", 33) ; // Don't allow faster than 30hz send rate 3216 3217 if ( time - lastVoiceSendtime < VOICE_THROTTLE_TIME_IN_MS ) { 3218 return; 3219 } 3220 3221 lastVoiceSendtime = time; 3222 3223 idStaticList< int, MAX_PLAYERS > localTalkers; 3224 3225 voiceChat->GetActiveLocalTalkers( localTalkers ); 3226 3227 for ( int i = 0; i < localTalkers.Num(); i++ ) { 3228 3229 // NOTE - For 360, we don't need more than XHV_MAX_VOICECHAT_PACKETS * XHV_VOICECHAT_MODE_PACKET_SIZE bytes 3230 const int MAX_VDP_DATA_SIZE = 1000; 3231 3232 byte buffer[MAX_VDP_DATA_SIZE]; 3233 3234 const int titleStorageDataSize = session->GetTitleStorageInt( "MAX_VDP_DATA_SIZE", 1000 ); 3235 const int dataSizeAvailable = Min< int >( titleStorageDataSize, sizeof( buffer ) ); 3236 3237 // in-out parameter 3238 int dataSize = dataSizeAvailable; 3239 if ( !voiceChat->GetLocalChatData( localTalkers[i], buffer, dataSize ) ) { 3240 continue; 3241 } 3242 assert( dataSize <= sizeof( buffer ) ); 3243 3244 idStaticList< const lobbyAddress_t *, MAX_PLAYERS > recipients; 3245 3246 voiceChat->GetRecipientsForTalker( localTalkers[i], recipients ); 3247 3248 for ( int j = 0; j < recipients.Num(); j++ ) { 3249 activeLobby->SendConnectionLess( *recipients[j], idLobby::OOB_VOICE_AUDIO, buffer, dataSize ); 3250 } 3251 } 3252 } 3253 3254 /* 3255 ======================== 3256 idSessionLocal::HandleOobVoiceAudio 3257 ======================== 3258 */ 3259 void idSessionLocal::HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) { 3260 3261 idLobby * activeLobby = GetActivePlatformLobby(); 3262 3263 if ( activeLobby == NULL ) { 3264 return; 3265 } 3266 3267 voiceChat->SetActiveLobby( activeLobby->lobbyType ); 3268 3269 voiceChat->SubmitIncomingChatData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() ); 3270 } 3271 3272 /* 3273 ======================== 3274 idSessionLocal::SetActiveChatGroup 3275 ======================== 3276 */ 3277 void idSessionLocal::SetActiveChatGroup( int groupIndex ) { 3278 voiceChat->SetActiveChatGroup( groupIndex ); 3279 } 3280 3281 /* 3282 ======================== 3283 idSessionLocal::GetLobbyUserVoiceState 3284 ======================== 3285 */ 3286 voiceState_t idSessionLocal::GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID ) { 3287 idLobby * activeLobby = GetActivePlatformLobby(); 3288 3289 if ( activeLobby == NULL ) { 3290 return VOICECHAT_STATE_NOT_TALKING; 3291 } 3292 3293 const lobbyUser_t * user = activeLobby->GetLobbyUserByID( lobbyUserID ); 3294 3295 if ( !verify( user != NULL ) ) { 3296 return VOICECHAT_STATE_NOT_TALKING; 3297 } 3298 3299 return voiceChat->GetVoiceState( user ); 3300 } 3301 3302 /* 3303 ======================== 3304 idSessionLocal::GetDisplayStateFromVoiceState 3305 ======================== 3306 */ 3307 voiceStateDisplay_t idSessionLocal::GetDisplayStateFromVoiceState( voiceState_t voiceState ) const { 3308 if ( ( GetState() == GAME_LOBBY && MatchTypeIsLocal( GetGameLobby().GetMatchParms().matchFlags ) ) 3309 || ( GetState() == PARTY_LOBBY && MatchTypeIsLocal( GetPartyLobby().GetMatchParms().matchFlags ) ) ) { 3310 return VOICECHAT_DISPLAY_NONE; // never show voice stuff in splitscreen 3311 } 3312 3313 switch ( voiceState ) { 3314 case VOICECHAT_STATE_MUTED_REMOTE: 3315 case VOICECHAT_STATE_MUTED_LOCAL: 3316 case VOICECHAT_STATE_MUTED_ALL: 3317 return VOICECHAT_DISPLAY_MUTED; 3318 case VOICECHAT_STATE_NOT_TALKING: 3319 return VOICECHAT_DISPLAY_NOTTALKING; 3320 case VOICECHAT_STATE_TALKING: 3321 return VOICECHAT_DISPLAY_TALKING; 3322 case VOICECHAT_STATE_TALKING_GLOBAL: 3323 return VOICECHAT_DISPLAY_TALKING_GLOBAL; 3324 case VOICECHAT_STATE_NO_MIC: 3325 default: 3326 return VOICECHAT_DISPLAY_NOTTALKING; 3327 } 3328 } 3329 3330 /* 3331 ======================== 3332 idSessionLocal::ToggleLobbyUserVoiceMute 3333 ======================== 3334 */ 3335 void idSessionLocal::ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID ) { 3336 idLobby * activeLobby = GetActivePlatformLobby(); 3337 3338 if ( activeLobby == NULL ) { 3339 return; 3340 } 3341 3342 // Get the master local user 3343 idLocalUser * masterUser = signInManager->GetMasterLocalUser(); 3344 3345 if ( masterUser == NULL ) { 3346 return; 3347 } 3348 3349 const lobbyUser_t * srcUser = activeLobby->GetLobbyUser( activeLobby->GetLobbyUserIndexByLocalUserHandle( masterUser->GetLocalUserHandle() ) ); 3350 3351 if ( srcUser == NULL ) { 3352 return; 3353 } 3354 3355 const lobbyUser_t * targetUser = activeLobby->GetLobbyUserByID( lobbyUserID ); 3356 3357 if ( !verify( targetUser != NULL ) ) { 3358 return; 3359 } 3360 3361 if ( srcUser == targetUser ) { 3362 return; // Can't toggle yourself 3363 } 3364 3365 voiceChat->ToggleMuteLocal( srcUser, targetUser ); 3366 } 3367 3368 /* 3369 ======================== 3370 idSessionLocal::UpdateMasterUserHeadsetState 3371 ======================== 3372 */ 3373 void idSessionLocal::UpdateMasterUserHeadsetState() 3374 { 3375 if ( GetState() != PARTY_LOBBY && GetState() != GAME_LOBBY && GetState() != INGAME ) { 3376 return; 3377 } 3378 3379 lobbyUser_t * user = GetActivePlatformLobby()->GetSessionUserFromLocalUser( signInManager->GetMasterLocalUser() ); 3380 3381 // TODO: Is this possible? 3382 if ( user == NULL ) { 3383 return; 3384 } 3385 3386 int talkerIndex = voiceChat->FindTalkerByUserId( user->lobbyUserID, GetActivePlatformLobby()->lobbyType ); 3387 bool voiceChanged = voiceChat->HasHeadsetStateChanged( talkerIndex ); 3388 3389 if ( voiceChanged ) { 3390 byte buffer[ idPacketProcessor::MAX_MSG_SIZE ]; 3391 idBitMsg msg( buffer, sizeof( buffer ) ); 3392 msg.WriteLong( 1 ); 3393 user->lobbyUserID.WriteToMsg( msg ); 3394 msg.WriteBool( voiceChat->GetHeadsetState( talkerIndex ) ); 3395 3396 idLib::Printf( "Sending voicestate %d for user %d %s\n", voiceChat->GetHeadsetState( talkerIndex ), talkerIndex, user->gamertag ); 3397 3398 if ( GetActivePlatformLobby()->IsHost() ) { 3399 for ( int p = 0; p < GetActivePlatformLobby()->peers.Num(); p++ ) { 3400 if ( GetActivePlatformLobby()->peers[p].IsConnected() ) { 3401 GetActivePlatformLobby()->QueueReliableMessage( p, idLobby::RELIABLE_HEADSET_STATE, msg.GetReadData(), msg.GetSize() ); 3402 } 3403 } 3404 3405 } else { 3406 GetActivePlatformLobby()->QueueReliableMessage( GetActivePlatformLobby()->host, idLobby::RELIABLE_HEADSET_STATE, msg.GetReadData(), msg.GetSize() ); 3407 } 3408 } 3409 3410 } 3411 3412 /* 3413 ======================== 3414 idSessionLocal::GetNumContentPackages 3415 ======================== 3416 */ 3417 int idSessionLocal::GetNumContentPackages() const { 3418 return downloadedContent.Num(); 3419 } 3420 3421 /* 3422 ======================== 3423 idSessionLocal::GetContentPackageID 3424 ======================== 3425 */ 3426 int idSessionLocal::GetContentPackageID( int contentIndex ) const { 3427 assert( contentIndex < MAX_CONTENT_PACKAGES ); 3428 3429 if ( downloadedContent[ contentIndex ].isMounted ) { 3430 return downloadedContent[ contentIndex ].dlcID; 3431 } 3432 3433 return 0; 3434 } 3435 3436 /* 3437 ======================== 3438 idSessionLocal::GetContentPackagePath 3439 ======================== 3440 */ 3441 const char * idSessionLocal::GetContentPackagePath( int contentIndex ) const { 3442 assert( contentIndex < MAX_CONTENT_PACKAGES ); 3443 3444 if ( downloadedContent[ contentIndex ].isMounted ) { 3445 return downloadedContent[ contentIndex ].rootPath.c_str(); 3446 } 3447 3448 return NULL; 3449 } 3450 3451 /* 3452 ======================== 3453 idSessionLocal::GetContentPackageIndexForID 3454 ======================== 3455 */ 3456 int idSessionLocal::GetContentPackageIndexForID( int contentID ) const { 3457 int contentIndex = -1; 3458 3459 for ( int i = 0; i < downloadedContent.Num(); i++ ) { 3460 if ( downloadedContent[i].dlcID == contentID ) { 3461 contentIndex = i; 3462 break; 3463 } 3464 } 3465 3466 return contentIndex; 3467 } 3468 3469 /* 3470 ======================== 3471 idSessionLocal::SetLobbyUserRelativeScore 3472 ======================== 3473 */ 3474 void idSessionLocal::SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) { 3475 // All platforms but 360 stub this out 3476 } 3477 3478 /* 3479 ======================== 3480 idSessionLocal::ReadTitleStorage 3481 ======================== 3482 */ 3483 void idSessionLocal::ReadTitleStorage( void * buffer, int bufferLen ) { 3484 // https://ps3.scedev.net/projects/ps3_sdk_docs/docs/ps3-en,NP_Lookup-Reference,sceNpLookupTitleSmallStorageAsync/1 3485 // If the file is not on the server, this will be handled as though a file of 0 bytes were on the server. 3486 // This means that 0 will be set to contentLength and 0 (for normal termination) will return for the return value. 3487 // This situation can occur with problems in actual operation, so the application must be designed not to hang up even in such situations 3488 //bufferLen = 0; 3489 3490 idLib::Printf( "ReadTitleStorage: %i bytes\n", bufferLen ); 3491 3492 #if !defined( ID_RETAIL ) || defined( ID_RETAIL_INTERNAL ) 3493 if ( net_ignoreTitleStorage.GetBool() ) {//&& idLib::GetProduction() < PROD_PRODUCTION ) { 3494 idLib::Printf( "ReadTitleStorage: *********************** IGNORING ********************\n" ); 3495 return; 3496 } 3497 #endif 3498 3499 //idScopedGlobalHeap everythingHereGoesInTheGlobalHeap; 3500 3501 idParser parser( LEXFL_NOERRORS | LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT ); 3502 parser.LoadMemory( ( const char* )buffer, bufferLen, "default.tss" ); 3503 3504 bool valid = true; 3505 3506 while ( true ) { 3507 idToken token; 3508 3509 if ( !parser.ReadToken( &token ) ) { 3510 break; 3511 } 3512 3513 if ( token.Icmp( "netvars" ) == 0 ) { 3514 if ( !titleStorageVars.Parse( parser ) ) { 3515 valid = false; 3516 break; 3517 } 3518 } else { 3519 valid = false; 3520 break; 3521 } 3522 } 3523 3524 if ( valid ) { 3525 titleStorageLoaded = true; 3526 idLib::Printf( "ReadTitleStorage: SUCCESS\n" ); 3527 titleStorageVars.Print(); 3528 } else { 3529 titleStorageLoaded = false; 3530 idLib::Printf( "ReadTitleStorage: FAILED\n" ); 3531 titleStorageVars.Clear(); 3532 } 3533 } 3534 3535 /* 3536 ======================== 3537 idSessionLocal::ReadDLCInfo 3538 ======================== 3539 */ 3540 bool idSessionLocal::ReadDLCInfo( idDict & dlcInfo, void * buffer, int bufferLen ) { 3541 idParser parser( LEXFL_NOERRORS | LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT ); 3542 parser.LoadMemory( ( const char* )buffer, bufferLen, "info.txt" ); 3543 3544 bool valid = true; 3545 3546 while ( true ) { 3547 idToken token; 3548 3549 if ( !parser.ReadToken( &token ) ) { 3550 break; 3551 } 3552 3553 if ( token.Icmp( "dlcInfo" ) == 0 ) { 3554 if ( !dlcInfo.Parse( parser ) ) { 3555 valid = false; 3556 break; 3557 } 3558 } else { 3559 valid = false; 3560 break; 3561 } 3562 } 3563 3564 return valid; 3565 } 3566 3567 /* 3568 ======================== 3569 idSessionLocal::IsPlatformPartyInLobby 3570 ======================== 3571 */ 3572 bool idSessionLocal::IsPlatformPartyInLobby() { 3573 idLocalUser * user = session->GetSignInManager().GetMasterLocalUser(); 3574 idLobby * lobby = GetActivePlatformLobby(); 3575 3576 if ( user == NULL || lobby == NULL ) { 3577 return false; 3578 } 3579 3580 if ( user->GetPartyCount() > MAX_PLAYERS || user->GetPartyCount() < 2 ) { 3581 return false; 3582 } 3583 3584 // TODO: Implement PC 3585 return false; 3586 } 3587 3588 /* 3589 ======================== 3590 idSessionLocal::PrePickNewHost 3591 This is called when we have determined that we need to pick a new host. 3592 Call PickNewHostInternal to continue on with the host picking process. 3593 ======================== 3594 */ 3595 void idSessionLocal::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) { 3596 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: (%s)\n", lobby.GetLobbyName() ); 3597 3598 if ( GetActivePlatformLobby() == NULL ) { 3599 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetActivePlatformLobby() == NULL (%s)\n", lobby.GetLobbyName() ); 3600 return; 3601 } 3602 3603 // Check to see if we can migrate AT ALL 3604 // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION) 3605 if ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) { 3606 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MATCH_PARTY_INVITE_PLACEHOLDER (%s)\n", lobby.GetLobbyName() ); 3607 3608 // Can't migrate, shut both lobbies down, and create a new match using the original parms 3609 GetGameStateLobby().Shutdown(); 3610 GetGameLobby().Shutdown(); 3611 GetPartyLobby().Shutdown(); 3612 3613 // Throw up the appropriate dialog message so the player knows what happeend 3614 if ( localState >= idSessionLocal::STATE_LOADING ) { 3615 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState >= idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() ); 3616 common->Dialog().AddDialog( GDM_BECAME_HOST_GAME_STATS_DROPPED, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); 3617 } else { 3618 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState < idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() ); 3619 common->Dialog().AddDialog( GDM_LOBBY_BECAME_HOST_GAME, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); 3620 } 3621 3622 CreateMatch( GetActivePlatformLobby()->parms ); 3623 3624 return; 3625 } 3626 3627 // Check to see if the match is searchable 3628 if ( GetState() >= idSession::GAME_LOBBY && MatchTypeIsSearchable( GetGameLobby().parms.matchFlags ) ) { 3629 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MatchTypeIsSearchable (%s)\n", lobby.GetLobbyName() ); 3630 // Searchable games migrate lobbies independently, and don't need to stay in sync 3631 lobby.PickNewHostInternal( forceMe, inviteOldHost ); 3632 return; 3633 } 3634 3635 // 3636 // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status 3637 // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs 3638 // 3639 3640 // Check to see if we should go back to a party lobby 3641 if ( GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY ) { 3642 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() ); 3643 // Force the party lobby to start picking a new host if we lost the game lobby host 3644 GetPartyLobby().PickNewHostInternal( forceMe, inviteOldHost ); 3645 3646 // End the game lobby, and go back to party lobby 3647 GetGameStateLobby().Shutdown(); 3648 GetGameLobby().Shutdown(); 3649 SetState( GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER ); 3650 } else { 3651 NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() < idSessionLocal::PARTY_LOBBY && GetState() != idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() ); 3652 if ( localState >= idSessionLocal::STATE_LOADING ) { 3653 common->Dialog().AddDialog( GDM_HOST_QUIT, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true ); // The host has quit the session. Returning to the main menu. 3654 } 3655 3656 // Go back to main menu 3657 GetGameLobby().Shutdown(); 3658 GetGameStateLobby().Shutdown(); 3659 GetPartyLobby().Shutdown(); 3660 SetState( idSessionLocal::STATE_IDLE ); 3661 } 3662 } 3663 /* 3664 ======================== 3665 idSessionLocal::PreMigrateInvite 3666 This is called just before we get invited to a migrated session 3667 If we return false, the invite will be ignored 3668 ======================== 3669 */ 3670 bool idSessionLocal::PreMigrateInvite( idLobby & lobby ) 3671 { 3672 if ( GetActivePlatformLobby() == NULL ) { 3673 return false; 3674 } 3675 3676 // Check to see if we can migrate AT ALL 3677 // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION) 3678 if ( !verify( ( GetPartyLobby().parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) { 3679 return false; // Shouldn't get invites for coop (we should make this a specific option (MATCH_ALLOW_MIGRATION)) 3680 } 3681 3682 // Check to see if the match is searchable 3683 if ( MatchTypeIsSearchable( GetGameLobby().parms.matchFlags ) ) { 3684 // Searchable games migrate lobbies independently, and don't need to stay in sync 3685 return true; 3686 } 3687 3688 // 3689 // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status 3690 // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs 3691 // 3692 3693 if ( lobby.lobbyType != idLobby::TYPE_PARTY ) { 3694 return false; // We shouldn't be getting invites from non party lobbies when in a non searchable game 3695 } 3696 3697 // Non placeholder Party lobbies can always migrate 3698 if ( GetBackState() >= idSessionLocal::PARTY_LOBBY ) { 3699 // Non searchable games go back to the party lobby 3700 GetGameLobby().Shutdown(); 3701 SetState( GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER ); 3702 } 3703 3704 return true; // Non placeholder Party lobby invites joinable 3705 } 3706 3707 /* 3708 ================================================================================================ 3709 lobbyAddress_t 3710 ================================================================================================ 3711 */ 3712 3713 /* 3714 ======================== 3715 lobbyAddress_t::lobbyAddress_t 3716 ======================== 3717 */ 3718 lobbyAddress_t::lobbyAddress_t() { 3719 memset( &netAddr, 0, sizeof( netAddr ) ); 3720 netAddr.type = NA_BAD; 3721 } 3722 3723 /* 3724 ======================== 3725 lobbyAddress_t::InitFromIPandPort 3726 ======================== 3727 */ 3728 void lobbyAddress_t::InitFromIPandPort( const char * ip, int port ) { 3729 Sys_StringToNetAdr( ip, &netAddr, true ); 3730 if ( !netAddr.port ) { 3731 netAddr.port = port; 3732 } 3733 } 3734 3735 3736 /* 3737 ======================== 3738 lobbyAddress_t::InitFromNetadr 3739 ======================== 3740 */ 3741 void lobbyAddress_t::InitFromNetadr( const netadr_t & netadr ) { 3742 assert( netadr.type != NA_BAD ); 3743 netAddr = netadr; 3744 } 3745 3746 /* 3747 ======================== 3748 lobbyAddress_t::ToString 3749 ======================== 3750 */ 3751 const char * lobbyAddress_t::ToString() const { 3752 return Sys_NetAdrToString( netAddr ); 3753 } 3754 3755 /* 3756 ======================== 3757 lobbyAddress_t::UsingRelay 3758 ======================== 3759 */ 3760 bool lobbyAddress_t::UsingRelay() const { 3761 return false; 3762 } 3763 3764 /* 3765 ======================== 3766 lobbyAddress_t::Compare 3767 ======================== 3768 */ 3769 bool lobbyAddress_t::Compare( const lobbyAddress_t & addr, bool ignoreSessionCheck ) const { 3770 return Sys_CompareNetAdrBase( netAddr, addr.netAddr ); 3771 } 3772 3773 /* 3774 ======================== 3775 lobbyAddress_t::WriteToMsg 3776 ======================== 3777 */ 3778 void lobbyAddress_t::WriteToMsg( idBitMsg & msg ) const { 3779 msg.WriteData( &netAddr, sizeof( netAddr ) ); 3780 } 3781 3782 /* 3783 ======================== 3784 lobbyAddress_t::ReadFromMsg 3785 ======================== 3786 */ 3787 void lobbyAddress_t::ReadFromMsg( idBitMsg & msg ) { 3788 msg.ReadData( &netAddr, sizeof( netAddr ) ); 3789 } 3790 3791 /* 3792 ================================================================================================ 3793 idNetSessionPort 3794 ================================================================================================ 3795 */ 3796 3797 /* 3798 ======================== 3799 idNetSessionPort::idNetSessionPort 3800 ======================== 3801 */ 3802 idNetSessionPort::idNetSessionPort() : 3803 forcePacketDropPrev( 0.0f ), 3804 forcePacketDropCurr( 0.0f ) 3805 { 3806 } 3807 3808 /* 3809 ======================== 3810 idNetSessionPort::InitPort 3811 ======================== 3812 */ 3813 bool idNetSessionPort::InitPort( int portNumber, bool useBackend ) { 3814 return UDP.InitForPort( portNumber ); 3815 } 3816 3817 /* 3818 ======================== 3819 idNetSessionPort::ReadRawPacket 3820 ======================== 3821 */ 3822 bool idNetSessionPort::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize ) { 3823 bool result = UDP.GetPacket( from.netAddr, data, size, maxSize ); 3824 3825 static idRandom2 random( Sys_Milliseconds() ); 3826 if ( net_forceDrop.GetInteger() != 0 ) { 3827 forcePacketDropCurr = random.RandomInt( 100 ); 3828 if ( net_forceDrop.GetInteger() >= forcePacketDropCurr ) { 3829 return false; 3830 } 3831 } 3832 3833 return result; 3834 } 3835 3836 /* 3837 ======================== 3838 idNetSessionPort::SendRawPacket 3839 ======================== 3840 */ 3841 void idNetSessionPort::SendRawPacket( const lobbyAddress_t & to, const void * data, int size ) { 3842 static idRandom2 random( Sys_Milliseconds() ); 3843 if ( net_forceDrop.GetInteger() != 0 && net_forceDrop.GetInteger() >= random.RandomInt( 100 ) ) { 3844 return; 3845 } 3846 assert( size <= idPacketProcessor::MAX_FINAL_PACKET_SIZE ); 3847 3848 UDP.SendPacket( to.netAddr, data, size ); 3849 } 3850 3851 /* 3852 ======================== 3853 idNetSessionPort::IsOpen 3854 ======================== 3855 */ 3856 bool idNetSessionPort::IsOpen() { 3857 return UDP.IsOpen(); 3858 } 3859 3860 /* 3861 ======================== 3862 idNetSessionPort::Close 3863 ======================== 3864 */ 3865 void idNetSessionPort::Close() { 3866 UDP.Close(); 3867 } 3868 3869 /* 3870 ================================================================================================ 3871 Commands 3872 ================================================================================================ 3873 */ 3874 3875 //==================================================================================== 3876 3877 CONSOLE_COMMAND( voicechat_mute, "TEMP", 0 ) { 3878 if ( args.Argc() != 2 ) { 3879 idLib::Printf( "Usage: voicechat_mute <user index>\n" ); 3880 return; 3881 } 3882 3883 int i = atoi( args.Argv( 1 ) ); 3884 session->ToggleLobbyUserVoiceMute( session->GetActivePlatformLobbyBase().GetLobbyUserIdByOrdinal( i ) ); 3885 } 3886 3887 /* 3888 ======================== 3889 force_disconnect_all 3890 ======================== 3891 */ 3892 CONSOLE_COMMAND( force_disconnect_all, "force disconnect on all users", 0 ) { 3893 session->GetSignInManager().RemoveAllLocalUsers(); 3894 } 3895 3896 /* 3897 ======================== 3898 void Net_DebugOutputSignedInUsers_f 3899 ======================== 3900 */ 3901 void Net_DebugOutputSignedInUsers_f( const idCmdArgs &args ) { 3902 session->GetSignInManager().DebugOutputLocalUserInfo(); 3903 } 3904 idCommandLink Net_DebugOutputSignedInUsers( "net_debugOutputSignedInUsers", Net_DebugOutputSignedInUsers_f, "Outputs all the local users and other debugging information from the sign in manager" ); 3905 3906 /* 3907 ======================== 3908 void Net_RemoveUserFromLobby_f 3909 ======================== 3910 */ 3911 void Net_RemoveUserFromLobby_f( const idCmdArgs &args ) { 3912 if ( args.Argc() > 1 ) { 3913 int localUserNum = atoi( args.Argv( 1 ) ); 3914 if ( localUserNum < session->GetSignInManager().GetNumLocalUsers() ) { 3915 session->GetSignInManager().RemoveLocalUserByIndex( localUserNum ); 3916 } else { 3917 idLib::Printf( "This user is not in the lobby\n" ); 3918 } 3919 } else { 3920 idLib::Printf( "Usage: net_RemoveUserFromLobby <localUserNum>\n" ); 3921 } 3922 } 3923 3924 idCommandLink Net_RemoveUserFromLobby( "net_removeUserFromLobby", Net_RemoveUserFromLobby_f, "Removes the given user from the lobby" ); 3925 3926 /* 3927 ======================== 3928 Net_dropClient 3929 ======================== 3930 */ 3931 CONSOLE_COMMAND( Net_DropClient, "Drop a client", 0 ) { 3932 if ( args.Argc() < 3 ) { 3933 idLib::Printf( "usage: Net_DropClient <clientnum> [<session>] 0/default: drop from game, 1: drop from party, otherwise drop from both\n" ); 3934 return; 3935 } 3936 int lobbyType = 0; 3937 if ( args.Argc() > 2 ) { 3938 lobbyType = atoi( args.Argv( 2 ) ); 3939 } 3940 session->DropClient( atoi( args.Argv(1) ), lobbyType ); 3941 } 3942 3943 /* 3944 ======================== 3945 idSessionLocal::DropClient 3946 ======================== 3947 */ 3948 void idSessionLocal::DropClient( int peerNum, int session ) { 3949 if ( session == 1 || session >= 2 ) { 3950 GetPartyLobby().DisconnectPeerFromSession( peerNum ); 3951 } 3952 if ( session == 0 || session >= 2 ) { 3953 GetGameLobby().DisconnectPeerFromSession( peerNum ); 3954 } 3955 } 3956 3957 /* 3958 ======================== 3959 idSessionLocal::ListServersCommon 3960 ======================== 3961 */ 3962 void idSessionLocal::ListServersCommon() { 3963 netadr_t broadcast; 3964 memset( &broadcast, 0, sizeof( broadcast ) ); 3965 broadcast.type = NA_BROADCAST; 3966 broadcast.port = net_port.GetInteger(); 3967 3968 lobbyAddress_t address; 3969 address.InitFromNetadr( broadcast ); 3970 3971 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; 3972 idBitMsg msg( buffer, sizeof( buffer ) ); 3973 3974 // Add the current version info to the query 3975 const unsigned long localChecksum = NetGetVersionChecksum(); 3976 3977 NET_VERBOSE_PRINT( "ListServers: Hash checksum: %i, broadcasting to: %s\n", localChecksum, address.ToString() ); 3978 3979 msg.WriteLong( localChecksum ); 3980 3981 GetPort(); 3982 // Send the query as a broadcast 3983 GetPartyLobby().SendConnectionLess( address, idLobby::OOB_MATCH_QUERY, msg.GetReadData(), msg.GetSize() ); 3984 } 3985 3986 /* 3987 ======================== 3988 idSessionLocal::HandleDedicatedServerQueryRequest 3989 ======================== 3990 */ 3991 void idSessionLocal::HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) { 3992 NET_VERBOSE_PRINT( "HandleDedicatedServerQueryRequest from %s\n", remoteAddr.ToString() ); 3993 3994 bool canJoin = true; 3995 3996 const unsigned long localChecksum = NetGetVersionChecksum(); 3997 const unsigned long remoteChecksum = msg.ReadLong(); 3998 3999 if ( remoteChecksum != localChecksum ) { 4000 NET_VERBOSE_PRINT( "HandleServerQueryRequest: Invalid version from %s\n", remoteAddr.ToString() ); 4001 canJoin = false; 4002 } 4003 4004 // Make sure we are the host of this party session 4005 if ( !GetPartyLobby().IsHost() ) { 4006 NET_VERBOSE_PRINT( "HandleServerQueryRequest: Not host of party\n" ); 4007 canJoin = false; 4008 } 4009 4010 // Make sure there is a session active 4011 if ( GetActivePlatformLobby() == NULL ) { 4012 canJoin = false; 4013 } 4014 4015 // Make sure we have enough free slots 4016 if ( GetPartyLobby().NumFreeSlots() == 0 || GetGameLobby().NumFreeSlots() == 0 ) { 4017 NET_VERBOSE_PRINT( "No free slots\n" ); 4018 canJoin = false; 4019 } 4020 4021 if ( MatchTypeInviteOnly( GetPartyLobby().parms.matchFlags ) ) { 4022 canJoin = false; 4023 } 4024 4025 // Buffer to hold reply msg 4026 byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ]; 4027 idBitMsg retmsg( buffer, sizeof( buffer ) ); 4028 4029 idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser(); 4030 4031 if ( masterUser == NULL && !net_headlessServer.GetBool() ) { 4032 canJoin = false; 4033 } 4034 4035 // Send the info about this game session to the caller 4036 retmsg.WriteBool( canJoin ); 4037 4038 if ( canJoin ) { 4039 serverInfo_t serverInfo; 4040 serverInfo.joinable = ( session->GetState() >= idSession::LOADING ); 4041 4042 if ( !net_headlessServer.GetBool() ) { 4043 serverInfo.serverName = masterUser->GetGamerTag(); 4044 } 4045 4046 if ( GetGameLobby().IsLobbyActive() ) { 4047 serverInfo.gameMap = GetGameLobby().parms.gameMap; 4048 serverInfo.gameMode = GetGameLobby().parms.gameMode; 4049 } else { 4050 serverInfo.gameMode = -1; 4051 } 4052 4053 serverInfo.numPlayers = GetActivePlatformLobby()->GetNumLobbyUsers(); 4054 serverInfo.maxPlayers = GetActivePlatformLobby()->parms.numSlots; 4055 serverInfo.Write( retmsg ); 4056 4057 for ( int i = 0; i < GetActivePlatformLobby()->GetNumLobbyUsers(); i++ ) { 4058 retmsg.WriteString( GetActivePlatformLobby()->GetLobbyUserName( GetActivePlatformLobby()->GetLobbyUserIdByOrdinal( i ) ) ); 4059 } 4060 } 4061 4062 // Send it 4063 GetPartyLobby().SendConnectionLess( remoteAddr, idLobby::OOB_MATCH_QUERY_ACK, retmsg.GetReadData(), retmsg.GetSize() ); 4064 } 4065 4066 /* 4067 ======================== 4068 idSessionLocal::HandleDedicatedServerQueryAck 4069 ======================== 4070 */ 4071 void idSessionLocal::HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) { 4072 NET_VERBOSE_PRINT( "HandleDedicatedServerQueryAck from %s\n", remoteAddr.ToString() ); 4073 dedicatedServerSearch->HandleQueryAck( remoteAddr, msg ); 4074 } 4075 4076 /* 4077 ======================== 4078 idSessionLocal::ServerPlayerList 4079 ======================== 4080 */ 4081 const idList< idStr > * idSessionLocal::ServerPlayerList( int i ) { 4082 return NULL; 4083 } 4084 4085 /* 4086 ======================== 4087 lobbyUserID_t::Serialize 4088 ======================== 4089 */ 4090 void lobbyUserID_t::Serialize( idSerializer & ser ) { 4091 localUserHandle.Serialize( ser ); 4092 ser.Serialize( lobbyType ); 4093 }