Session.cpp 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  17. 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.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Session_local.h"
  23. #include "../sys/sys_session_savegames.h"
  24. #include "../sys/sys_voicechat.h"
  25. /*
  26. ========================
  27. SteamAPIDebugTextHook
  28. ========================
  29. */
  30. extern "C" void __cdecl SteamAPIDebugTextHook( int nSeverity, const char * pchDebugText ) {
  31. // if you're running in the debugger, only warnings (nSeverity >= 1) will be sent
  32. // if you add -debug_steamapi to the command-line, a lot of extra informational messages will also be sent
  33. idLib::Printf( "%s", pchDebugText );
  34. if ( nSeverity >= 1 ) {
  35. // place to set a breakpoint for catching API errors
  36. int x = 3;
  37. x;
  38. }
  39. }
  40. /*
  41. ===============================================================================
  42. SESSION LOCAL
  43. ===============================================================================
  44. */
  45. /*
  46. ===============
  47. idSessionLocal::idSessionLocal
  48. ===============
  49. */
  50. idSessionLocal::idSessionLocal() {
  51. localState = STATE_PRESS_START;
  52. InitBaseState();
  53. }
  54. /*
  55. ===============
  56. idSessionLocal::~idSessionLocal
  57. ===============
  58. */
  59. idSessionLocal::~idSessionLocal() {
  60. if ( sessionCallbacks != NULL ) {
  61. delete sessionCallbacks;
  62. }
  63. }
  64. /*
  65. ========================
  66. idSessionLocal::InitBaseState
  67. ========================
  68. */
  69. void idSessionLocal::InitBaseState() {
  70. localState = STATE_PRESS_START;
  71. sessionOptions = 0;
  72. currentID = 0;
  73. sessionCallbacks = new idSessionLocalCallbacks( this );
  74. connectType = CONNECT_NONE;
  75. isSysUIShowing = false;
  76. pendingInviteDevice = 0;
  77. pendingInviteMode = PENDING_INVITE_NONE;
  78. flushedStats = false;
  79. enumerationHandle = 0;
  80. }
  81. /*
  82. ===============
  83. idSessionLocal::Shutdown
  84. ===============
  85. */
  86. void idSessionLocal::Shutdown() {
  87. delete signInManager;
  88. if ( achievementSystem != NULL ) {
  89. achievementSystem->Shutdown();
  90. delete achievementSystem;
  91. }
  92. DestroySteamObjects();
  93. }
  94. /*
  95. ===============
  96. idSessionLocal::Init
  97. Called in an orderly fashion at system startup,
  98. so commands, cvars, files, etc are all available
  99. ===============
  100. */
  101. void idSessionLocal::Init() {
  102. common->Printf( "-------- Initializing Session --------\n" );
  103. InitSteam();
  104. ConstructSteamObjects();
  105. signInManager = new idSignInManagerWin();
  106. achievementSystem = new idAchievementSystemWin();
  107. achievementSystem->Init();
  108. Initialize();
  109. common->Printf( "session initialized\n" );
  110. common->Printf( "--------------------------------------\n" );
  111. }
  112. /*
  113. ========================
  114. idSessionLocal::InitSteam
  115. ========================
  116. */
  117. void idSessionLocal::InitSteam() {
  118. if ( steamInitialized || steamFailed ) {
  119. if ( steamFailed ) {
  120. net_usePlatformBackend.SetBool( false );
  121. }
  122. return;
  123. }
  124. steamInitialized = SteamAPI_Init();
  125. steamFailed = !steamInitialized;
  126. if ( steamFailed ) {
  127. if ( net_usePlatformBackend.GetBool() ) {
  128. idLib::Warning( "Steam failed to initialize. Usually this happens because the Steam client isn't running." );
  129. // Turn off the usage of steam if it fails to initialize
  130. // FIXME: We'll want to bail (nicely) in the shipping product most likely
  131. net_usePlatformBackend.SetBool( false );
  132. }
  133. return;
  134. }
  135. // from now on, all Steam API functions should return non-null interface pointers
  136. assert( SteamUtils() );
  137. SteamUtils()->SetWarningMessageHook( &SteamAPIDebugTextHook );
  138. ConstructSteamObjects();
  139. }
  140. /*
  141. ========================
  142. idSessionLocal::ConstructSteamObjects
  143. ========================
  144. */
  145. void idSessionLocal::ConstructSteamObjects() {
  146. }
  147. /*
  148. ========================
  149. idSessionLocal::DestroySteamObjects
  150. ========================
  151. */
  152. void idSessionLocal::DestroySteamObjects() {
  153. }
  154. /*
  155. ========================
  156. idSessionLocal::MoveToPressStart
  157. ========================
  158. */
  159. void idSessionLocal::MoveToPressStart( gameDialogMessages_t msg ) {
  160. if ( localState != STATE_PRESS_START ) {
  161. MoveToPressStart();
  162. common->Dialog().ClearDialogs();
  163. common->Dialog().AddDialog( msg, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true );
  164. }
  165. }
  166. /*
  167. ========================
  168. idSessionLocal::MoveToPressStart
  169. ========================
  170. */
  171. void idSessionLocal::MoveToPressStart() {
  172. if ( localState != STATE_PRESS_START ) {
  173. GetSignInManager().RemoveAllLocalUsers();
  174. SetState( STATE_PRESS_START );
  175. }
  176. }
  177. /*
  178. ========================
  179. idSessionLocal::SetState
  180. ========================
  181. */
  182. void idSessionLocal::SetState( idSessionLocal::state_t newState ) {
  183. assert( newState < NUM_STATES );
  184. assert( localState < NUM_STATES );
  185. if ( newState == localState ) {
  186. return;
  187. }
  188. localState = newState;
  189. }
  190. /*
  191. ========================
  192. idSessionLocal::GetState
  193. ========================
  194. */
  195. idSessionLocal::sessionState_t idSessionLocal::GetState() const {
  196. // Convert our internal state to one of the external states
  197. switch ( localState ) {
  198. case STATE_PRESS_START: return PRESS_START;
  199. case STATE_IDLE: return IDLE;
  200. case STATE_PARTY_LOBBY_HOST: return PARTY_LOBBY;
  201. case STATE_PARTY_LOBBY_PEER: return PARTY_LOBBY;
  202. case STATE_GAME_LOBBY_HOST: return GAME_LOBBY;
  203. case STATE_GAME_LOBBY_PEER: return GAME_LOBBY;
  204. //case STATE_GAME_STATE_LOBBY_HOST: return GAME_LOBBY;
  205. //case STATE_GAME_STATE_LOBBY_PEER: return GAME_LOBBY;
  206. case STATE_LOADING: return LOADING;
  207. case STATE_INGAME: return INGAME;
  208. case STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY: return CONNECTING;
  209. case STATE_CREATE_AND_MOVE_TO_GAME_LOBBY: return CONNECTING;
  210. //case STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY: return CONNECTING;
  211. case STATE_FIND_OR_CREATE_MATCH: return SEARCHING;
  212. case STATE_CONNECT_AND_MOVE_TO_PARTY: return CONNECTING;
  213. case STATE_CONNECT_AND_MOVE_TO_GAME: return CONNECTING;
  214. //case STATE_CONNECT_AND_MOVE_TO_GAME_STATE: return CONNECTING;
  215. case STATE_BUSY: return BUSY;
  216. default: {
  217. idLib::Error( "GetState: Unknown state in idSessionLocal" );
  218. return IDLE;
  219. }
  220. };
  221. }
  222. /*
  223. ========================
  224. idSessionLocal::Pump
  225. ========================
  226. */
  227. void idSessionLocal::Pump() {
  228. GetSignInManager().Pump();
  229. idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser();
  230. if ( masterUser != NULL && localState == STATE_PRESS_START ) {
  231. // If we have a master user, and we are at press start, move to the menu area
  232. SetState( STATE_IDLE );
  233. }
  234. GetAchievementSystem().Pump();
  235. }
  236. /*
  237. ========================
  238. idSessionLocal::OnMasterLocalUserSignin
  239. ========================
  240. */
  241. void idSessionLocal::OnMasterLocalUserSignin() {
  242. enumerationHandle = EnumerateSaveGames( 0 );
  243. }
  244. /*
  245. ========================
  246. idSessionLocal::LoadGame
  247. ========================
  248. */
  249. saveGameHandle_t idSessionLocal::LoadGame( const char * name, const idList< idSaveFileEntry > & files ) {
  250. if ( processorLoadFiles.InitLoadFiles( name, files ) ) {
  251. return saveGameManager.ExecuteProcessor( &processorLoadFiles );
  252. } else {
  253. return 0;
  254. }
  255. }
  256. /*
  257. ========================
  258. idSessionLocal::SaveGame
  259. ========================
  260. */
  261. saveGameHandle_t idSessionLocal::SaveGame( const char * name, const idList< idSaveFileEntry > & files, const idSaveGameDetails & description, uint64 skipErrorMask ) {
  262. saveGameHandle_t ret = 0;
  263. // serialize the description file behind their back...
  264. idList< idSaveFileEntry > filesWithDetails( files );
  265. idFile_Memory * gameDetailsFile = new idFile_Memory( SAVEGAME_DETAILS_FILENAME );
  266. //gameDetailsFile->MakeWritable();
  267. description.descriptors.WriteToIniFile( gameDetailsFile );
  268. filesWithDetails.Append( idSaveFileEntry( gameDetailsFile, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE, SAVEGAME_DETAILS_FILENAME ) );
  269. if ( processorSave.InitSave( name, filesWithDetails, description ) ) {
  270. processorSave.SetSkipSystemErrorDialogMask( skipErrorMask );
  271. ret = GetSaveGameManager().ExecuteProcessor( &processorSave );
  272. }
  273. return ret;
  274. }
  275. /*
  276. ========================
  277. idSessionLocal::EnumerateSaveGames
  278. ========================
  279. */
  280. saveGameHandle_t idSessionLocal::EnumerateSaveGames( uint64 skipErrorMask ) {
  281. saveGameHandle_t ret = 0;
  282. // flush the old enumerated list
  283. GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear();
  284. if ( processorEnumerate.Init() ) {
  285. processorEnumerate.SetSkipSystemErrorDialogMask( skipErrorMask );
  286. ret = GetSaveGameManager().ExecuteProcessor( &processorEnumerate );
  287. }
  288. return ret;
  289. }
  290. /*
  291. ========================
  292. idSessionLocal::DeleteSaveGame
  293. ========================
  294. */
  295. saveGameHandle_t idSessionLocal::DeleteSaveGame( const char * name, uint64 skipErrorMask ) {
  296. saveGameHandle_t ret = 0;
  297. if ( processorDelete.InitDelete( name ) ) {
  298. processorDelete.SetSkipSystemErrorDialogMask( skipErrorMask );
  299. ret = GetSaveGameManager().ExecuteProcessor( &
  300. processorDelete );
  301. }
  302. return ret;
  303. }
  304. /*
  305. ========================
  306. idSessionLocal::IsEnumerating
  307. ========================
  308. */
  309. bool idSessionLocal::IsEnumerating() const {
  310. return !session->IsSaveGameCompletedFromHandle( processorEnumerate.GetHandle() );
  311. }
  312. /*
  313. ========================
  314. idSessionLocal::GetEnumerationHandle
  315. ========================
  316. */
  317. saveGameHandle_t idSessionLocal::GetEnumerationHandle() const {
  318. return processorEnumerate.GetHandle();
  319. }
  320. /*
  321. ========================
  322. idSessionLocal::CancelSaveGameWithHandle
  323. ========================
  324. */
  325. void idSessionLocal::CancelSaveGameWithHandle( const saveGameHandle_t & handle ) {
  326. GetSaveGameManager().CancelWithHandle( handle );
  327. }
  328. // FIXME: Move to sys_stats.cpp
  329. leaderboardDefinition_t * registeredLeaderboards[MAX_LEADERBOARDS];
  330. int numRegisteredLeaderboards = 0;
  331. /*
  332. ========================
  333. Sys_FindLeaderboardDef
  334. ========================
  335. */
  336. const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id ) {
  337. for ( int i = 0; i < numRegisteredLeaderboards; i++ ) {
  338. if ( registeredLeaderboards[i]->id == id ) {
  339. return registeredLeaderboards[i];
  340. }
  341. }
  342. return NULL;
  343. }
  344. /*
  345. ========================
  346. idSessionLocal::GetActiveLobby
  347. ========================
  348. */
  349. idLobby * idSessionLocal::GetActiveLobby() {
  350. sessionState_t state = GetState();
  351. if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) {
  352. return &GetGameLobby();
  353. } else if ( state == PARTY_LOBBY ) {
  354. return &GetPartyLobby();
  355. }
  356. return NULL;
  357. }
  358. /*
  359. ========================
  360. idSessionLocal::GetActiveLobby
  361. ========================
  362. */
  363. const idLobby * idSessionLocal::GetActiveLobby() const {
  364. sessionState_t state = GetState();
  365. if ( ( state == GAME_LOBBY ) || ( state == BUSY ) || ( state == INGAME ) || ( state == LOADING ) ) {
  366. return &GetGameLobby();
  367. } else if ( state == PARTY_LOBBY ) {
  368. return &GetPartyLobby();
  369. }
  370. return NULL;
  371. }
  372. /*
  373. ========================
  374. idSessionLocal::GetActiveLobbyBase
  375. This returns the base version for the idSession version
  376. ========================
  377. */
  378. idLobbyBase & idSessionLocal::GetActiveLobbyBase() {
  379. idLobby * activeLobby = GetActiveLobby();
  380. if ( activeLobby != NULL ) {
  381. return *activeLobby;
  382. }
  383. return stubLobby; // So we can return at least something
  384. }
  385. /*
  386. ========================
  387. idSessionLocal::PrePickNewHost
  388. This is called when we have determined that we need to pick a new host.
  389. Call PickNewHostInternal to continue on with the host picking process.
  390. ========================
  391. */
  392. void idSessionLocal::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) {
  393. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: (%s)\n", lobby.GetLobbyName() );
  394. if ( GetActiveLobby() == NULL ) {
  395. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetActiveLobby() == NULL (%s)\n", lobby.GetLobbyName() );
  396. return;
  397. }
  398. // Check to see if we can migrate AT ALL
  399. // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION)
  400. if ( GetPartyLobby().parms.GetSessionMatchFlags() & MATCH_PARTY_INVITE_PLACEHOLDER ) {
  401. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MATCH_PARTY_INVITE_PLACEHOLDER (%s)\n", lobby.GetLobbyName() );
  402. // Can't migrate, shut both lobbies down, and create a new match using the original parms
  403. GetGameLobby().Shutdown();
  404. GetPartyLobby().Shutdown();
  405. // Throw up the appropriate dialog message so the player knows what happeend
  406. if ( localState >= STATE_LOADING ) {
  407. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState >= idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() );
  408. common->Dialog().AddDialog( GDM_BECAME_HOST_GAME_STATS_DROPPED, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
  409. } else {
  410. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: localState < idSessionLocal::STATE_LOADING (%s)\n", lobby.GetLobbyName() );
  411. common->Dialog().AddDialog( GDM_LOBBY_BECAME_HOST_GAME, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
  412. }
  413. CreateMatch( GetActiveLobby()->parms );
  414. return;
  415. }
  416. // Check to see if the match is searchable
  417. if ( GetState() >= idSession::GAME_LOBBY && MatchTypeIsSearchable( GetGameLobby().parms.GetSessionMatchFlags() ) ) {
  418. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: MatchTypeIsSearchable (%s)\n", lobby.GetLobbyName() );
  419. // Searchable games migrate lobbies independently, and don't need to stay in sync
  420. lobby.PickNewHostInternal( forceMe, inviteOldHost );
  421. return;
  422. }
  423. //
  424. // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status
  425. // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs
  426. //
  427. // Check to see if we should go back to a party lobby
  428. if ( GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY ) {
  429. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() >= idSessionLocal::PARTY_LOBBY || GetState() == idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() );
  430. // Force the party lobby to start picking a new host if we lost the game lobby host
  431. GetPartyLobby().PickNewHostInternal( forceMe, inviteOldHost );
  432. // End the game lobby, and go back to party lobby
  433. GetGameLobby().Shutdown();
  434. SetState( GetPartyLobby().IsHost() ? STATE_PARTY_LOBBY_HOST : STATE_PARTY_LOBBY_PEER );
  435. } else {
  436. NET_VERBOSE_PRINT("idSessionLocal::PrePickNewHost: GetBackState() < idSessionLocal::PARTY_LOBBY && GetState() != idSession::PARTY_LOBBY (%s)\n", lobby.GetLobbyName() );
  437. if ( localState >= STATE_LOADING ) {
  438. 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.
  439. }
  440. // Go back to main menu
  441. GetGameLobby().Shutdown();
  442. GetPartyLobby().Shutdown();
  443. SetState( STATE_IDLE );
  444. }
  445. }
  446. /*
  447. ========================
  448. idSessionLocal::PreMigrateInvite
  449. This is called just before we get invited to a migrated session
  450. If we return false, the invite will be ignored
  451. ========================
  452. */
  453. bool idSessionLocal::PreMigrateInvite( idLobby & lobby )
  454. {
  455. if ( GetActiveLobby() == NULL ) {
  456. return false;
  457. }
  458. // Check to see if we can migrate AT ALL
  459. // This is checking for coop, we should make this a specific option (MATCH_ALLOW_MIGRATION)
  460. if ( !verify( ( GetPartyLobby().parms.GetSessionMatchFlags() & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) {
  461. return false; // Shouldn't get invites for coop (we should make this a specific option (MATCH_ALLOW_MIGRATION))
  462. }
  463. // Check to see if the match is searchable
  464. if ( MatchTypeIsSearchable( GetGameLobby().parms.GetSessionMatchFlags() ) ) {
  465. // Searchable games migrate lobbies independently, and don't need to stay in sync
  466. return true;
  467. }
  468. //
  469. // Beyond this point, game lobbies must be sync'd with party lobbies as far as host status
  470. // So to enforce that, we pull you out of the game lobby if you are in one when migration occurs
  471. //
  472. if ( lobby.lobbyType != idLobby::TYPE_PARTY ) {
  473. return false; // We shouldn't be getting invites from non party lobbies when in a non searchable game
  474. }
  475. // Non placeholder Party lobbies can always migrate
  476. if ( GetBackState() >= idSessionLocal::PARTY_LOBBY ) {
  477. // Non searchable games go back to the party lobby
  478. GetGameLobby().Shutdown();
  479. SetState( GetPartyLobby().IsHost() ? STATE_PARTY_LOBBY_HOST : STATE_PARTY_LOBBY_PEER );
  480. }
  481. return true; // Non placeholder Party lobby invites joinable
  482. }
  483. /*
  484. ========================
  485. idSessionLocal::HandleDedicatedServerQueryRequest
  486. ========================
  487. */
  488. void idSessionLocal::HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) {
  489. NET_VERBOSE_PRINT( "HandleDedicatedServerQueryRequest from %s\n", remoteAddr.ToString() );
  490. bool canJoin = true;
  491. const unsigned long localChecksum = 0;
  492. const unsigned long remoteChecksum = msg.ReadLong();
  493. if ( remoteChecksum != localChecksum ) {
  494. NET_VERBOSE_PRINT( "HandleServerQueryRequest: Invalid version from %s\n", remoteAddr.ToString() );
  495. canJoin = false;
  496. }
  497. // Make sure we are the host of this party session
  498. if ( !GetPartyLobby().IsHost() ) {
  499. NET_VERBOSE_PRINT( "HandleServerQueryRequest: Not host of party\n" );
  500. canJoin = false;
  501. }
  502. // Make sure there is a session active
  503. if ( GetActiveLobby() == NULL ) {
  504. canJoin = false;
  505. }
  506. // Make sure we have enough free slots
  507. if ( GetPartyLobby().NumFreeSlots() == 0 || GetGameLobby().NumFreeSlots() == 0 ) {
  508. NET_VERBOSE_PRINT( "No free slots\n" );
  509. canJoin = false;
  510. }
  511. if ( MatchTypeInviteOnly( GetPartyLobby().parms.GetSessionMatchFlags() ) ) {
  512. canJoin = false;
  513. }
  514. // Buffer to hold reply msg
  515. byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
  516. idBitMsg retmsg;
  517. retmsg.InitWrite( buffer, sizeof( buffer ) );
  518. idLocalUser * masterUser = GetSignInManager().GetMasterLocalUser();
  519. if ( masterUser == NULL ) {
  520. canJoin = false;
  521. }
  522. // Send the info about this game session to the caller
  523. retmsg.WriteBool( canJoin );
  524. if ( canJoin ) {
  525. retmsg.WriteBool( session->GetState() >= idSession::LOADING );
  526. retmsg.WriteString( masterUser->GetGamerTag() );
  527. retmsg.WriteLong( GetActiveLobby()->parms.GetGameType() ); // We need to write out the game type whether we are in a game or not
  528. if ( GetGameLobby().IsSessionActive() ) {
  529. retmsg.WriteLong( GetGameLobby().parms.GetGameMap() );
  530. retmsg.WriteLong( GetGameLobby().parms.GetGameMode() );
  531. } else {
  532. retmsg.WriteLong( -1 );
  533. retmsg.WriteLong( -1 );
  534. }
  535. retmsg.WriteLong( GetActiveLobby()->GetNumLobbyUsers() );
  536. retmsg.WriteLong( GetActiveLobby()->parms.GetNumSlots() );
  537. for ( int i = 0; i < GetActiveLobby()->GetNumLobbyUsers(); i++ ) {
  538. retmsg.WriteString( GetActiveLobby()->GetLobbyUserName( i ) );
  539. }
  540. }
  541. // Send it
  542. GetPartyLobby().SendConnectionLess( remoteAddr, idLobby::OOB_MATCH_QUERY_ACK, retmsg.GetWriteData(), retmsg.GetSize() );
  543. }
  544. /*
  545. ========================
  546. idSessionLocal::HandleDedicatedServerQueryAck
  547. ========================
  548. */
  549. void idSessionLocal::HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) {
  550. NET_VERBOSE_PRINT( "HandleDedicatedServerQueryAck from %s\n", remoteAddr.ToString() );
  551. }
  552. /*
  553. ========================
  554. idSessionLocal::StartSessions
  555. ========================
  556. */
  557. void idSessionLocal::StartSessions() {
  558. if ( GetPartyLobby().lobbyBackend != NULL ) {
  559. GetPartyLobby().lobbyBackend->StartSession();
  560. }
  561. if ( GetGameLobby().lobbyBackend != NULL ) {
  562. GetGameLobby().lobbyBackend->StartSession();
  563. }
  564. SetLobbiesAreJoinable( false );
  565. }
  566. /*
  567. ========================
  568. idSessionLocal::EndSessions
  569. ========================
  570. */
  571. void idSessionLocal::EndSessions() {
  572. if ( GetPartyLobby().lobbyBackend != NULL ) {
  573. GetPartyLobby().lobbyBackend->EndSession();
  574. }
  575. if ( GetGameLobby().lobbyBackend != NULL ) {
  576. GetGameLobby().lobbyBackend->EndSession();
  577. }
  578. SetLobbiesAreJoinable( true );
  579. }
  580. /*
  581. ========================
  582. idSessionLocal::SetLobbiesAreJoinable
  583. ========================
  584. */
  585. void idSessionLocal::SetLobbiesAreJoinable( bool joinable ) {
  586. // NOTE - We don't manipulate the joinable state when we are supporting join in progress
  587. // Lobbies will naturally be non searchable when there are no free slots
  588. if ( GetPartyLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetPartyLobby().parms.GetSessionMatchFlags() ) ) {
  589. NET_VERBOSE_PRINT( "Party lobbyBackend SetIsJoinable: %d\n", joinable );
  590. GetPartyLobby().lobbyBackend->SetIsJoinable( joinable );
  591. }
  592. if ( GetGameLobby().lobbyBackend != NULL && !MatchTypeIsJoinInProgress( GetGameLobby().parms.GetSessionMatchFlags() ) ) {
  593. GetGameLobby().lobbyBackend->SetIsJoinable( joinable );
  594. NET_VERBOSE_PRINT( "Game lobbyBackend SetIsJoinable: %d\n", joinable );
  595. }
  596. }
  597. /*
  598. ========================
  599. idSessionLocal::EndMatchForMigration
  600. ========================
  601. */
  602. void idSessionLocal::EndMatchForMigration() {
  603. ClearVoiceGroups();
  604. }
  605. /*
  606. ========================
  607. idSessionLocal::ClearVoiceGroups
  608. ========================
  609. */
  610. void idSessionLocal::ClearVoiceGroups() {
  611. /*
  612. for ( int i = 0; i < GetGameLobby().GetNumLobbyUsers(); ++i ) {
  613. SetGameSessionUserChatGroup( i, 0 );
  614. }
  615. SetActiveChatGroup( 0 );
  616. */
  617. }
  618. /*
  619. ========================
  620. idSessionLocal::GoodbyeFromHost
  621. ========================
  622. */
  623. void idSessionLocal::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) {
  624. if ( !verify( localState > STATE_IDLE ) ) {
  625. 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() );
  626. MoveToMainMenu();
  627. return; // Ignore if we are not past the main menu
  628. }
  629. // Goodbye from host. See if we were connecting vs connected
  630. if ( ( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME ) && lobby.peers[peerNum].GetConnectionState() == idLobby::CONNECTION_CONNECTING ) {
  631. // We were denied a connection attempt
  632. idLib::Printf( "NET: Denied connection attempt from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType );
  633. // This will try to move to the next connection if one exists, otherwise will create a match
  634. HandleConnectionFailed( lobby, msgType == idLobby::OOB_GOODBYE_FULL );
  635. } else {
  636. // We were disconnected from a server we were previously connected to
  637. idLib::Printf( "NET: Disconnected from host %s on session %s. MsgType %i.\n", remoteAddress.ToString(), lobby.GetLobbyName(), msgType );
  638. const bool leaveGameWithParty = ( msgType == idLobby::OOB_GOODBYE_W_PARTY );
  639. if ( leaveGameWithParty && lobby.lobbyType == idLobby::TYPE_GAME && lobby.IsPeer() && GetState() == idSession::GAME_LOBBY && GetPartyLobby().host >= 0 &&
  640. lobby.peers[peerNum].address.Compare( GetPartyLobby().peers[GetPartyLobby().host].address, true ) ) {
  641. // If a host is telling us goodbye from a game lobby, and the game host is the same as our party host,
  642. // and we aren't in a game, and the host wants us to leave with him, then do so now
  643. GetGameLobby().Shutdown();
  644. SetState( STATE_PARTY_LOBBY_PEER );
  645. } else {
  646. // Host left, so pick a new host (possibly even us) for this lobby
  647. lobby.PickNewHost();
  648. }
  649. }
  650. }
  651. /*
  652. ========================
  653. idSessionLocal::HandlePackets
  654. ========================
  655. */
  656. bool idSessionLocal::HandlePackets() {
  657. byte packetBuffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
  658. lobbyAddress_t remoteAddress;
  659. int recvSize = 0;
  660. while ( ReadRawPacket( remoteAddress, packetBuffer, recvSize, sizeof( packetBuffer ) ) && recvSize > 0 ) {
  661. // fragMsg will hold the raw packet
  662. idBitMsg fragMsg;
  663. fragMsg.InitRead( packetBuffer, recvSize );
  664. // Peek at the session ID
  665. idPacketProcessor::sessionId_t sessionID = idPacketProcessor::GetSessionID( fragMsg );
  666. // idLib::Printf( "NET: HandlePackets - session %d, size %d \n", sessionID, recvSize );
  667. // Make sure it's valid
  668. if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) {
  669. idLib::Printf( "NET: Invalid sessionID %s.\n", remoteAddress.ToString() );
  670. continue;
  671. }
  672. // Distribute the packet to the proper lobby
  673. if ( sessionID & 1 ) {
  674. GetGameLobby().HandlePacket( remoteAddress, fragMsg, sessionID );
  675. } else {
  676. GetPartyLobby().HandlePacket( remoteAddress, fragMsg, sessionID );
  677. }
  678. }
  679. return false;
  680. }
  681. idCVar net_connectTimeoutInSeconds( "net_connectTimeoutInSeconds", "15", CVAR_INTEGER, "timeout (in seconds) while connecting" );
  682. idCVar net_testPartyMemberConnectFail( "net_testPartyMemberConnectFail", "-1", CVAR_INTEGER, "Force this party member index to fail to connect to games." );
  683. /*
  684. ========================
  685. idSessionLocal::HandleConnectAndMoveToLobby
  686. Called from State_Connect_And_Move_To_Party/State_Connect_And_Move_To_Game
  687. ========================
  688. */
  689. bool idSessionLocal::HandleConnectAndMoveToLobby( idLobby & lobby ) {
  690. assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME );
  691. assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT );
  692. if ( lobby.GetState() == idLobby::STATE_FAILED ) {
  693. // 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)
  694. HandleConnectionFailed( lobby, false );
  695. return true;
  696. }
  697. if ( lobby.GetState() != idLobby::STATE_IDLE ) {
  698. return HandlePackets(); // Valid but busy
  699. }
  700. assert( !GetPartyLobby().waitForPartyOk );
  701. //
  702. // Past this point, we've connected to the lobby
  703. //
  704. // 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
  705. if ( lobby.lobbyType == idLobby::TYPE_GAME ) {
  706. if ( GetPartyLobby().IsHost() ) {
  707. // As a host, wait until all party members make it
  708. assert( !GetGameLobby().waitForPartyOk );
  709. const int timeoutMs = net_connectTimeoutInSeconds.GetInteger() * 1000;
  710. if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) {
  711. // Took too long, move to next result, or create a game instead
  712. HandleConnectionFailed( lobby, false );
  713. return true;
  714. }
  715. int numUsersIn = 0;
  716. for ( int i = 0; i < GetPartyLobby().GetNumLobbyUsers(); i++ ) {
  717. if ( net_testPartyMemberConnectFail.GetInteger() == i ) {
  718. continue;
  719. }
  720. bool foundUser = false;
  721. lobbyUser_t * partyUser = GetPartyLobby().GetLobbyUser( i );
  722. for ( int j = 0; j < GetGameLobby().GetNumLobbyUsers(); j++ ) {
  723. lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( j );
  724. if ( GetGameLobby().IsSessionUserLocal( gameUser ) || gameUser->address.Compare( partyUser->address, true ) ) {
  725. numUsersIn++;
  726. foundUser = true;
  727. break;
  728. }
  729. }
  730. assert( !GetPartyLobby().IsSessionUserIndexLocal( i ) || foundUser );
  731. }
  732. if ( numUsersIn != GetPartyLobby().GetNumLobbyUsers() ) {
  733. return HandlePackets(); // All users not in, keep waiting until all user make it, or we time out
  734. }
  735. NET_VERBOSE_PRINT( "NET: All party members made it into the game lobby.\n" );
  736. // Let all the party members know everyone made it, and it's ok to stay at this server
  737. for ( int i = 0; i < GetPartyLobby().peers.Num(); i++ ) {
  738. if ( GetPartyLobby().peers[ i ].IsConnected() ) {
  739. GetPartyLobby().QueueReliableMessage( i, idLobby::RELIABLE_PARTY_CONNECT_OK );
  740. }
  741. }
  742. } else {
  743. if ( !verify ( lobby.host != -1 ) ) {
  744. MoveToMainMenu();
  745. connectType = CONNECT_NONE;
  746. return false;
  747. }
  748. // As a peer, wait for server to tell us everyone made it
  749. if ( GetGameLobby().waitForPartyOk ) {
  750. const int timeoutMs = net_connectTimeoutInSeconds.GetInteger() * 1000;
  751. if ( timeoutMs != 0 && Sys_Milliseconds() - lobby.helloStartTime > timeoutMs ) {
  752. GetGameLobby().waitForPartyOk = false; // Just connect to this game lobby if we haven't heard from the party host for the entire timeout duration
  753. }
  754. }
  755. if ( GetGameLobby().waitForPartyOk ) {
  756. return HandlePackets(); // Waiting on party host to tell us everyone made it
  757. }
  758. }
  759. }
  760. // Success
  761. SetState( lobby.lobbyType == idLobby::TYPE_PARTY ? STATE_PARTY_LOBBY_PEER : STATE_GAME_LOBBY_PEER );
  762. connectType = CONNECT_NONE;
  763. return false;
  764. }
  765. /*
  766. ========================
  767. idSessionLocal::MoveToMainMenu
  768. ========================
  769. */
  770. void idSessionLocal::MoveToMainMenu() {
  771. GetPartyLobby().Shutdown();
  772. GetGameLobby().Shutdown();
  773. SetState( STATE_IDLE );
  774. }
  775. /*
  776. ========================
  777. idSessionLocal::HandleConnectionFailed
  778. Called anytime a connection fails, and does the right thing.
  779. ========================
  780. */
  781. void idSessionLocal::HandleConnectionFailed( idLobby & lobby, bool wasFull ) {
  782. assert( localState == STATE_CONNECT_AND_MOVE_TO_PARTY || localState == STATE_CONNECT_AND_MOVE_TO_GAME );
  783. assert( connectType == CONNECT_FIND_OR_CREATE || connectType == CONNECT_DIRECT );
  784. bool canPlayOnline = true;
  785. // Check for online status (this is only a problem on the PS3 at the moment. The 360 LIVE system handles this for us
  786. if ( GetSignInManager().GetMasterLocalUser() != NULL ) {
  787. canPlayOnline = GetSignInManager().GetMasterLocalUser()->CanPlayOnline();
  788. }
  789. if ( connectType == CONNECT_FIND_OR_CREATE ) {
  790. // Clear the "Lobby was Full" dialog in case it's up
  791. // We only want to see this msg when doing a direct connect (CONNECT_DIRECT)
  792. common->Dialog().ClearDialog( GDM_LOBBY_FULL );
  793. assert( localState == STATE_CONNECT_AND_MOVE_TO_GAME );
  794. assert( lobby.lobbyType == idLobby::TYPE_GAME );
  795. if ( !lobby.ConnectToNextSearchResult() ) {
  796. CreateMatch( GetGameLobby().parms ); // Assume any time we are connecting to a game lobby, it is from a FindOrCreateMatch call, so create a match
  797. }
  798. } else if ( connectType == CONNECT_DIRECT ) {
  799. if ( localState == STATE_CONNECT_AND_MOVE_TO_GAME && GetPartyLobby().IsPeer() ) {
  800. int flags = GetPartyLobby().parms.GetSessionMatchFlags();
  801. if ( MatchTypeIsOnline( flags ) && ( flags & MATCH_REQUIRE_PARTY_LOBBY ) && ( ( flags & MATCH_PARTY_INVITE_PLACEHOLDER ) == 0 ) ) {
  802. // We get here when our party host told us to connect to a game, but the game didn't exist.
  803. // Just drop back to the party lobby and wait for further orders.
  804. SetState( STATE_PARTY_LOBBY_PEER );
  805. return;
  806. }
  807. }
  808. if ( wasFull ) {
  809. common->Dialog().AddDialog( GDM_LOBBY_FULL, DIALOG_ACCEPT, NULL, NULL, false );
  810. } else if ( !canPlayOnline ) {
  811. common->Dialog().AddDialog( GDM_PLAY_ONLINE_NO_PROFILE, DIALOG_ACCEPT, NULL, NULL, false );
  812. } else {
  813. // 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
  814. // session local that the game was full so we don't do this check here
  815. // eeubanks: Pollard, how do you think we should handle this?
  816. if ( !common->Dialog().HasDialogMsg( GDM_LOBBY_FULL, NULL ) ) {
  817. common->Dialog().AddDialog( GDM_INVALID_INVITE, DIALOG_ACCEPT, NULL, NULL, false );
  818. }
  819. }
  820. MoveToMainMenu();
  821. } else {
  822. // Shouldn't be possible, but just in case
  823. MoveToMainMenu();
  824. }
  825. }
  826. /*
  827. ========================
  828. idSessionLocal::SendRawPacket
  829. ========================
  830. */
  831. void idSessionLocal::SendRawPacket( const lobbyAddress_t & to, const void * data, int size ) {
  832. GetPort().SendRawPacket( to, data, size );
  833. }
  834. /*
  835. ========================
  836. idSessionLocal::ReadRawPacket
  837. ========================
  838. */
  839. bool idSessionLocal::ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize ) {
  840. return GetPort().ReadRawPacket( from, data, size, maxSize );
  841. }
  842. /*
  843. ========================
  844. idSessionLocal::ConnectAndMoveToLobby
  845. ========================
  846. */
  847. void idSessionLocal::ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite ) {
  848. // Since we are connecting directly to a lobby, make sure no search results are left over from previous FindOrCreateMatch results
  849. // 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
  850. lobby.searchResults.Clear();
  851. // Attempt to connect to the lobby
  852. lobby.ConnectTo( connectInfo, fromInvite );
  853. connectType = CONNECT_DIRECT;
  854. // Wait for connection
  855. SetState( lobby.lobbyType == idLobby::TYPE_PARTY ? STATE_CONNECT_AND_MOVE_TO_PARTY : STATE_CONNECT_AND_MOVE_TO_GAME );
  856. }
  857. /*
  858. ========================
  859. idSessionLocal::WriteLeaderboardToMsg
  860. ========================
  861. */
  862. void idSessionLocal::WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats ) {
  863. assert( Sys_FindLeaderboardDef( leaderboard->id ) == leaderboard );
  864. msg.WriteLong( leaderboard->id );
  865. for ( int i = 0; i < leaderboard->numColumns; ++i ) {
  866. uint64 value = stats[i].value;
  867. //idLib::Printf( "value = %i\n", (int32)value );
  868. for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) {
  869. msg.WriteBits( value & 1, 1 );
  870. value >>= 1;
  871. }
  872. //msg.WriteData( &stats[i].value, sizeof( stats[i].value ) );
  873. }
  874. }
  875. /*
  876. ========================
  877. idSessionLocal::ReadLeaderboardFromMsg
  878. ========================
  879. */
  880. const leaderboardDefinition_t * idSessionLocal::ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats ) {
  881. int id = msg.ReadLong();
  882. const leaderboardDefinition_t * leaderboard = Sys_FindLeaderboardDef( id );
  883. if ( leaderboard == NULL ) {
  884. idLib::Printf( "NET: Invalid leaderboard id: %i\n", id );
  885. return NULL;
  886. }
  887. for ( int i = 0; i < leaderboard->numColumns; ++i ) {
  888. uint64 value = 0;
  889. for ( int j = 0; j < leaderboard->columnDefs[i].bits; j++ ) {
  890. value |= (uint64)( msg.ReadBits( 1 ) & 1 ) << j;
  891. }
  892. stats[i].value = value;
  893. //idLib::Printf( "value = %i\n", (int32)value );
  894. //msg.ReadData( &stats[i].value, sizeof( stats[i].value ) );
  895. }
  896. return leaderboard;
  897. }
  898. /*
  899. ========================
  900. idSessionLocal::EndMatchInternal
  901. ========================
  902. */
  903. void idSessionLocal::EndMatchInternal( bool premature/*=false*/ ) {
  904. ClearVoiceGroups();
  905. for ( int p = 0; p < GetGameLobby().peers.Num(); p++ ) {
  906. // If we are the host, increment the session ID. The client will use a rolling check to catch it
  907. if ( GetGameLobby().IsHost() ) {
  908. if ( GetGameLobby().peers[p].IsConnected() ) {
  909. if ( GetGameLobby().peers[p].packetProc != NULL ) {
  910. GetGameLobby().peers[p].packetProc->VerifyEmptyReliableQueue( idLobby::RELIABLE_GAME_DATA, idLobby::RELIABLE_DUMMY_MSG );
  911. }
  912. GetGameLobby().peers[p].sessionID = GetGameLobby().IncrementSessionID( GetGameLobby().peers[p].sessionID );
  913. }
  914. }
  915. GetGameLobby().peers[p].ResetMatchData();
  916. }
  917. GetGameLobby().loaded = false;
  918. //gameLobbyWasCoalesced = false; // Reset this back to false. We use this so the lobby code doesn't randomly choose a map when we coalesce
  919. ClearMigrationState();
  920. if ( common->IsMultiplayer() ) {
  921. if ( GetGameLobby().IsSessionActive() ) {
  922. // All peers need to remove disconnected users to stay in sync
  923. GetGameLobby().CompactDisconnectedUsers();
  924. // Go back to the game lobby
  925. if ( GetGameLobby().IsHost() ) {
  926. SetState( STATE_GAME_LOBBY_HOST );
  927. } else {
  928. SetState( STATE_GAME_LOBBY_PEER );
  929. }
  930. } else {
  931. // Oops, no game lobby?
  932. assert( false ); // how is this possible?
  933. MoveToMainMenu();
  934. }
  935. } else {
  936. SetState( STATE_IDLE );
  937. }
  938. if ( GetGameLobby().IsHost() ) {
  939. // Send a reliable msg to all peers to also "EndMatch"
  940. for ( int p = 0; p < GetGameLobby().peers.Num(); p++ ) {
  941. GetGameLobby().QueueReliableMessage( p, premature ? idLobby::RELIABLE_ENDMATCH_PREMATURE : idLobby::RELIABLE_ENDMATCH );
  942. }
  943. } else if( premature ) {
  944. // Notify client that host left early and thats why we are back in the lobby
  945. bool stats = MatchTypeHasStats( GetGameLobby().GetMatchParms().GetSessionMatchFlags() ) && ( GetFlushedStats() == false );
  946. common->Dialog().AddDialog( stats ? GDM_HOST_RETURNED_TO_LOBBY_STATS_DROPPED : GDM_HOST_RETURNED_TO_LOBBY, DIALOG_ACCEPT, NULL, NULL, false, __FUNCTION__, __LINE__, true );
  947. }
  948. }
  949. /*
  950. ========================
  951. idSessionLocal::HandleOobVoiceAudio
  952. ========================
  953. */
  954. void idSessionLocal::HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) {
  955. idLobby * activeLobby = GetActiveLobby();
  956. if ( activeLobby == NULL ) {
  957. return;
  958. }
  959. voiceChat->SetActiveLobby( activeLobby->lobbyType );
  960. voiceChat->SubmitIncomingChatData( msg.GetReadData() + msg.GetReadCount(), msg.GetSize() - msg.GetReadCount() );
  961. }
  962. /*
  963. ========================
  964. idSessionLocal::ClearMigrationState
  965. ========================
  966. */
  967. void idSessionLocal::ClearMigrationState() {
  968. // We are ending the match without migration, so clear that state
  969. GetPartyLobby().ResetAllMigrationState();
  970. GetGameLobby().ResetAllMigrationState();
  971. }
  972. /*
  973. ========================
  974. idSessionLocal::SendLeaderboardStatsToPlayer
  975. ========================
  976. */
  977. void idSessionLocal::SendLeaderboardStatsToPlayer( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, const column_t * stats ) {
  978. if ( GetGameLobby().IsLobbyUserDisconnected( sessionUserIndex ) ) {
  979. idLib::Warning( "Tried to tell disconnected user to report stats" );
  980. return;
  981. }
  982. const int peerIndex = GetGameLobby().PeerIndexFromLobbyUserIndex( sessionUserIndex );
  983. if ( peerIndex == -1 ) {
  984. idLib::Warning( "Tried to tell invalid peer index to report stats" );
  985. return;
  986. }
  987. if ( !verify( GetGameLobby().IsHost() ) ||
  988. !verify( peerIndex < GetGameLobby().peers.Num() ) ||
  989. !verify( GetGameLobby().peers[ peerIndex ].IsConnected() ) ) {
  990. idLib::Warning( "Tried to tell invalid peer to report stats" );
  991. return;
  992. }
  993. NET_VERBOSE_PRINT( "Telling sessionUserIndex %i (peer %i) to report stats\n", sessionUserIndex, peerIndex );
  994. lobbyUser_t * gameUser = GetGameLobby().GetLobbyUser( sessionUserIndex );
  995. if ( !verify( gameUser != NULL ) ) {
  996. return;
  997. }
  998. byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
  999. idBitMsg msg;
  1000. msg.InitWrite( buffer, sizeof( buffer ) );
  1001. // Use the user ID
  1002. msg.WriteLong( gameUser->userID );
  1003. WriteLeaderboardToMsg( msg, leaderboard, stats );
  1004. GetGameLobby().QueueReliableMessage( peerIndex, idLobby::RELIABLE_POST_STATS, msg.GetWriteData(), msg.GetSize() );
  1005. }
  1006. /*
  1007. ========================
  1008. idSessionLocal::RecvLeaderboardStatsForPlayer
  1009. ========================
  1010. */
  1011. void idSessionLocal::RecvLeaderboardStatsForPlayer( idBitMsg & msg ) {
  1012. column_t stats[ MAX_LEADERBOARD_COLUMNS ];
  1013. int userID = msg.ReadLong();
  1014. int sessionUserIndex = GetGameLobby().FindSessionUserByUserId( userID );
  1015. const leaderboardDefinition_t * leaderboard = ReadLeaderboardFromMsg( msg, stats );
  1016. if ( leaderboard == NULL ) {
  1017. idLib::Printf( "RecvLeaderboardStatsForPlayer: Invalid lb.\n" );
  1018. return;
  1019. }
  1020. LeaderboardUpload( sessionUserIndex, leaderboard, stats );
  1021. }