Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2364 lines
59 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Handles joining clients together in a matchmaking session before a multiplayer
  4. // game, tracking new players and dropped players during the game, and reporting
  5. // game results and stats after the game is complete.
  6. //
  7. //=============================================================================//
  8. #include "proto_oob.h"
  9. #include "matchmaking.h"
  10. #include "matchmakingqos.h"
  11. #include "Session.h"
  12. #include "vgui_baseui_interface.h"
  13. #include "cdll_engine_int.h"
  14. #include "convar.h"
  15. #include "cmd.h"
  16. #include "iclient.h"
  17. #include "server.h"
  18. #include "host.h"
  19. #if defined( _X360 )
  20. #include "xbox/xbox_win32stubs.h"
  21. #include "audio/private/snd_dev_xaudio.h"
  22. #include "audio_pch.h"
  23. #endif
  24. // memdbgon must be the last include file in a .cpp file!!!
  25. #include "tier0/memdbgon.h"
  26. static CMatchmaking s_Matchmaking;
  27. CMatchmaking *g_pMatchmaking = &s_Matchmaking;
  28. extern IVEngineClient *engineClient;
  29. extern IXboxSystem *g_pXboxSystem;
  30. // Expose an interface for GameUI
  31. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMatchmaking, IMatchmaking, VENGINE_MATCHMAKING_VERSION, s_Matchmaking );
  32. bool Channel_LessFunc( const uint &a, const uint &b )
  33. {
  34. return a < b;
  35. }
  36. //-----------------------------------------------------------------------------
  37. // Purpose: Constructor
  38. //-----------------------------------------------------------------------------
  39. CMatchmaking::CMatchmaking() : m_Channels( Channel_LessFunc )
  40. {
  41. m_bPreventFullServerStartup = false;
  42. m_bCleanup = false;
  43. m_bEnteredLobby = false;
  44. m_nTotalTeams = 0;
  45. m_pSearchResults = NULL;
  46. m_pSessionKeys = new KeyValues( "SessionKeys" );
  47. m_Session.SetParent( this );
  48. m_CurrentState = MMSTATE_INITIAL;
  49. m_InviteState = INVITE_NONE;
  50. memset( &m_InviteWaitingInfo, 0, sizeof( m_InviteWaitingInfo ) );
  51. }
  52. CMatchmaking::~CMatchmaking()
  53. {
  54. Cleanup();
  55. m_pSessionKeys->deleteThis();
  56. }
  57. //-----------------------------------------------------------------------------
  58. // Purpose: Cleanup the matchmaking class to enable re-entry
  59. //-----------------------------------------------------------------------------
  60. void CMatchmaking::Cleanup()
  61. {
  62. m_bInitialized = false;
  63. m_bCleanup = false;
  64. m_bEnteredLobby = false;
  65. m_Host.Clear();
  66. #ifdef _X360
  67. if ( Audio_GetXVoice() )
  68. {
  69. CClientInfo *pLocal = NULL;
  70. if ( m_bCreatedLocalTalker )
  71. {
  72. pLocal = &m_Local;
  73. }
  74. Audio_GetXVoice()->RemoveAllTalkers( pLocal );
  75. }
  76. #endif
  77. m_bCreatedLocalTalker = false;
  78. SetPreventFullServerStartup( false, "Cleanup\n" );
  79. m_Session.ResetSession();
  80. // TODO: Check on overlapped operations and cancel them
  81. // g_pXboxSystem->CancelAsyncOperations();
  82. ClearSearchResults();
  83. m_pSessionKeys->Clear();
  84. m_pGameServer = NULL;
  85. Q_memset( m_Mutelist, 0, sizeof( m_Mutelist ) );
  86. for ( int i = 0; i < MAX_PLAYERS_PER_CLIENT; ++i )
  87. {
  88. m_MutedBy[i].Purge();
  89. }
  90. m_Channels.RemoveAll();
  91. m_SessionContexts.Purge();
  92. m_SessionProperties.Purge();
  93. m_PlayerStats.Purge();
  94. m_Remote.PurgeAndDeleteElements();
  95. m_nGameSize = 0;
  96. m_nPrivateSlots = 0;
  97. m_nSendCount = 0;
  98. m_fHeartbeatInterval = HEARTBEAT_INTERVAL_SHORT;
  99. m_fNextHeartbeatTime = GetTime();
  100. }
  101. int CMatchmaking::FindOrCreateContext( const uint id )
  102. {
  103. int idx = m_SessionContexts.InvalidIndex();
  104. for ( int i = 0; i < m_SessionContexts.Count(); ++i )
  105. {
  106. if ( m_SessionContexts[i].dwContextId == id )
  107. {
  108. idx = i;
  109. }
  110. }
  111. if ( !m_SessionContexts.IsValidIndex( idx ) )
  112. {
  113. idx = m_SessionContexts.AddToTail();
  114. }
  115. return idx;
  116. }
  117. int CMatchmaking::FindOrCreateProperty( const uint id )
  118. {
  119. int idx = m_SessionProperties.InvalidIndex();
  120. for ( int i = 0; i < m_SessionProperties.Count(); ++i )
  121. {
  122. if ( m_SessionProperties[i].dwPropertyId == id )
  123. {
  124. idx = i;
  125. }
  126. }
  127. if ( !m_SessionProperties.IsValidIndex( idx ) )
  128. {
  129. idx = m_SessionProperties.AddToTail();
  130. }
  131. return idx;
  132. }
  133. //-----------------------------------------------------------------------------
  134. // Purpose: Add an additional property to the current session
  135. //-----------------------------------------------------------------------------
  136. void CMatchmaking::AddSessionProperty( const uint nType, const char *pID, const char *pValue, const char *pValueType )
  137. {
  138. KeyValues *pProperty = m_pSessionKeys->FindKey( pID, true );
  139. pProperty->SetName( pID );
  140. pProperty->SetInt( "type", nType );
  141. pProperty->SetString( "valuestring", pValue );
  142. pProperty->SetString( "valuetype", pValueType );
  143. AddSessionPropertyInternal( pProperty );
  144. }
  145. //-----------------------------------------------------------------------------
  146. // Purpose: Set properties and contexts for the current session
  147. //-----------------------------------------------------------------------------
  148. void CMatchmaking::SetSessionProperties( KeyValues *pPropertyKeys )
  149. {
  150. m_SessionContexts.RemoveAll();
  151. m_SessionProperties.RemoveAll();
  152. m_pSessionKeys->Clear();
  153. pPropertyKeys->CopySubkeys( m_pSessionKeys );
  154. for ( KeyValues *pProperty = m_pSessionKeys->GetFirstSubKey(); pProperty != NULL; pProperty = pProperty->GetNextKey() )
  155. {
  156. AddSessionPropertyInternal( pProperty );
  157. }
  158. }
  159. //-----------------------------------------------------------------------------
  160. // Purpose:
  161. //-----------------------------------------------------------------------------
  162. void CMatchmaking::AddSessionPropertyInternal( KeyValues *pProperty )
  163. {
  164. const char *pID = pProperty->GetName();
  165. const char *pValue = pProperty->GetString( "valuestring" );
  166. switch( pProperty->GetInt( "type" ) )
  167. {
  168. case SESSION_CONTEXT:
  169. {
  170. Msg( "Adding Context: %s : %s\n", pID, pValue );
  171. int id = g_ClientDLL->GetPresenceID( pID );
  172. int val = g_ClientDLL->GetPresenceID( pValue );
  173. int idx = FindOrCreateContext( id );
  174. XUSER_CONTEXT &ctx = m_SessionContexts[idx];
  175. ctx.dwContextId = id;
  176. ctx.dwValue = val;
  177. // Set the display string for gameUI
  178. char szBuffer[MAX_PATH];
  179. g_ClientDLL->GetPropertyDisplayString( ctx.dwContextId, ctx.dwValue, szBuffer, sizeof( szBuffer ) );
  180. pProperty->SetString( "displaystring", szBuffer );
  181. // X360TBD: Such game specifics as this shouldn't be hard-coded
  182. if ( m_CurrentState != MMSTATE_INITIAL && !Q_stricmp( pID, "CONTEXT_SCENARIO" ) )
  183. {
  184. // Set the scenario in our host data structure
  185. Q_strncpy( m_HostData.scenario, szBuffer, sizeof( m_HostData.scenario ) );
  186. UpdateSessionReplyData( XNET_QOS_LISTEN_SET_DATA );
  187. }
  188. }
  189. break;
  190. case SESSION_PROPERTY:
  191. {
  192. Msg( "Adding Property: %s : %s\n", pID, pValue );
  193. if ( !Q_stricmp( pID, "PROPERTY_PRIVATE_SLOTS" ) )
  194. {
  195. // "Private Slots" is not a search criteria
  196. m_nPrivateSlots = atoi( pValue );
  197. break;
  198. }
  199. int id = g_ClientDLL->GetPresenceID( pID );
  200. int idx = FindOrCreateProperty( id );
  201. XUSER_PROPERTY &prop = m_SessionProperties[idx];
  202. prop.dwPropertyId = id;
  203. if ( !Q_stricmp( pProperty->GetString( "valuetype" ), "int" ) )
  204. {
  205. prop.value.nData = atoi( pValue );
  206. prop.value.type = XUSER_DATA_TYPE_INT32;
  207. }
  208. // Build out the property keyvalues for gameUI
  209. char szBuffer[MAX_PATH];
  210. g_ClientDLL->GetPropertyDisplayString( prop.dwPropertyId, prop.value.nData, szBuffer, sizeof( szBuffer ) );
  211. pProperty->SetString( "displaystring", szBuffer );
  212. // X360TBD: Such game specifics as these shouldn't be so hard-coded
  213. if ( !Q_stricmp( pID, "PROPERTY_GAME_SIZE" ) )
  214. {
  215. m_nGameSize = atoi( pValue );
  216. }
  217. if ( !Q_stricmp( pID, "PROPERTY_NUMBER_OF_TEAMS" ) )
  218. {
  219. m_nTotalTeams = atoi( pValue );
  220. }
  221. if ( m_CurrentState != MMSTATE_INITIAL && !Q_stricmp( pID, "PROPERTY_MAX_GAME_TIME" ) )
  222. {
  223. // Set the game time in our host data structure
  224. m_HostData.gameTime = prop.value.nData;
  225. UpdateSessionReplyData( XNET_QOS_LISTEN_SET_DATA );
  226. }
  227. }
  228. break;
  229. case SESSION_FLAG:
  230. m_Session.SetFlag( g_ClientDLL->GetPresenceID( pID ) );
  231. break;
  232. default:
  233. Warning( "Session option type %d not recognized/n", pProperty->GetInt( "type" ) );
  234. break;
  235. }
  236. }
  237. KeyValues *CMatchmaking::GetSessionProperties()
  238. {
  239. return m_pSessionKeys;
  240. }
  241. double CMatchmaking::GetTime()
  242. {
  243. return Plat_FloatTime();
  244. }
  245. //-----------------------------------------------------------------------------
  246. // Purpose: At netchannel connection, register the messages
  247. //-----------------------------------------------------------------------------
  248. void CMatchmaking::ConnectionStart( INetChannel *chan )
  249. {
  250. REGISTER_MM_MSG( JoinResponse );
  251. REGISTER_MM_MSG( ClientInfo );
  252. REGISTER_MM_MSG( RegisterResponse );
  253. REGISTER_MM_MSG( Migrate );
  254. REGISTER_MM_MSG( Mutelist );
  255. REGISTER_MM_MSG( Checkpoint );
  256. REGISTER_MM_MSG( Heartbeat );
  257. REGISTER_CLC_MSG( VoiceData );
  258. }
  259. //-----------------------------------------------------------------------------
  260. // Purpose: Process a networked voice packet
  261. //-----------------------------------------------------------------------------
  262. bool CMatchmaking::ProcessVoiceData( CLC_VoiceData *pVoice )
  263. {
  264. char chReceived[4096];
  265. DWORD dwLength = pVoice->m_nLength;
  266. pVoice->m_DataIn.ReadBits( chReceived, dwLength );
  267. if ( m_Session.IsHost() )
  268. {
  269. char chCopyBuffer[4096];
  270. // Forward this message on to everyone else
  271. pVoice->m_DataOut.StartWriting( chCopyBuffer, sizeof ( chCopyBuffer ) );
  272. Q_memcpy( chCopyBuffer, chReceived, sizeof( chCopyBuffer ) );
  273. pVoice->m_DataOut.SeekToBit( dwLength );
  274. SendToRemoteClients( pVoice, true, pVoice->m_xuid );
  275. }
  276. // Playback the voice data locally through xaudio
  277. #if defined ( _X360 )
  278. if ( pVoice->m_xuid != m_Local.m_xuids[0] )
  279. {
  280. Audio_GetXVoice()->PlayIncomingVoiceData( pVoice->m_xuid, (byte*)chReceived, dwLength );
  281. }
  282. #endif
  283. return true;
  284. }
  285. //-----------------------------------------------------------------------------
  286. // Purpose: Delete channels that have been marked for deletion
  287. //-----------------------------------------------------------------------------
  288. void CMatchmaking::CleanupMarkedChannels()
  289. {
  290. // Clean up net channels that need to be deleted
  291. for ( int i = 0; i < m_ChannelsToRemove.Count(); ++i )
  292. {
  293. INetChannel *pNetChannel = FindChannel( m_ChannelsToRemove[i] );
  294. if ( pNetChannel )
  295. {
  296. if ( !m_Channels.Remove( m_ChannelsToRemove[i] ) )
  297. {
  298. Warning( "CleanupMarkedChannels: Failed to remove a channel!\n" );
  299. }
  300. }
  301. }
  302. m_ChannelsToRemove.Purge();
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Purpose: Channels can be flagged for deletion during packet processing.
  306. // Now that processing is finished, delete them.
  307. //-----------------------------------------------------------------------------
  308. void CMatchmaking::PacketEnd()
  309. {
  310. CleanupMarkedChannels();
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Purpose: Find a specific client from a netchannel
  314. //-----------------------------------------------------------------------------
  315. CClientInfo *CMatchmaking::FindClient( netadr_t *adr )
  316. {
  317. CClientInfo *pClient = NULL;
  318. unsigned int ip = adr->GetIPNetworkByteOrder();
  319. if ( ip == m_Host.m_adr.GetIPNetworkByteOrder() )
  320. {
  321. pClient = &m_Host;
  322. }
  323. else
  324. {
  325. for ( int i = 0; i < m_Remote.Count(); ++i )
  326. {
  327. if ( ip == m_Remote[i]->m_adr.GetIPNetworkByteOrder() )
  328. {
  329. pClient = m_Remote[i];
  330. break;
  331. }
  332. }
  333. }
  334. return pClient;
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Purpose: Find a specific client by his XUID
  338. //-----------------------------------------------------------------------------
  339. CClientInfo *CMatchmaking::FindClientByXUID( XUID xuid )
  340. {
  341. CClientInfo *pClient = NULL;
  342. if ( xuid == m_Host.m_xuids[0] )
  343. {
  344. pClient = &m_Host;
  345. }
  346. else
  347. {
  348. for ( int i = 0; i < m_Remote.Count(); ++i )
  349. {
  350. if ( xuid == m_Remote[i]->m_xuids[0] )
  351. {
  352. pClient = m_Remote[i];
  353. break;
  354. }
  355. }
  356. }
  357. return pClient;
  358. }
  359. //-----------------------------------------------------------------------------
  360. // Purpose: Find a specific client's netchannel
  361. //-----------------------------------------------------------------------------
  362. INetChannel *CMatchmaking::FindChannel( const unsigned int ip )
  363. {
  364. INetChannel *pChannel = NULL;
  365. int idx = m_Channels.Find( ip );
  366. if ( idx != m_Channels.InvalidIndex() )
  367. {
  368. pChannel = m_Channels.Element( idx );
  369. }
  370. return pChannel;
  371. }
  372. //-----------------------------------------------------------------------------
  373. // Purpose: Create a new netchannel
  374. //-----------------------------------------------------------------------------
  375. INetChannel *CMatchmaking::CreateNetChannel( netadr_t *adr )
  376. {
  377. INetChannel *pNewChannel = FindChannel( adr->GetIPNetworkByteOrder() );
  378. if ( !pNewChannel )
  379. {
  380. pNewChannel = NET_CreateNetChannel( NS_MATCHMAKING, adr, "MATCHMAKING", this );
  381. }
  382. if( pNewChannel )
  383. {
  384. // Set a rate limit and other relevant properties
  385. pNewChannel->SetTimeout( HEARTBEAT_TIMEOUT );
  386. }
  387. return pNewChannel;
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose: Add a netchannel for a session client
  391. //-----------------------------------------------------------------------------
  392. INetChannel *CMatchmaking::AddRemoteChannel( netadr_t *adr )
  393. {
  394. INetChannel *pNetChannel = CreateNetChannel( adr );
  395. if ( pNetChannel )
  396. {
  397. // Save this new channel
  398. m_Channels.Insert( adr->GetIPNetworkByteOrder(), pNetChannel );
  399. }
  400. return pNetChannel;
  401. }
  402. //-----------------------------------------------------------------------------
  403. // Purpose: Remove a netchannel for a session client
  404. //-----------------------------------------------------------------------------
  405. void CMatchmaking::RemoveRemoteChannel( netadr_t *adr, const char *pReason )
  406. {
  407. INetChannel *pNetChannel = FindChannel( adr->GetIPNetworkByteOrder() );
  408. if ( pNetChannel )
  409. {
  410. m_Channels.Remove( adr->GetIPNetworkByteOrder() );
  411. }
  412. }
  413. //-----------------------------------------------------------------------------
  414. // Purpose: Mark a net channel to be removed
  415. //-----------------------------------------------------------------------------
  416. void CMatchmaking::MarkChannelForRemoval( netadr_t *adr )
  417. {
  418. m_ChannelsToRemove.AddToTail( adr->GetIPNetworkByteOrder() );
  419. }
  420. //-----------------------------------------------------------------------------
  421. // Purpose: Set the timeout for a net channel
  422. //-----------------------------------------------------------------------------
  423. void CMatchmaking::SetChannelTimeout( netadr_t *adr, int timeout )
  424. {
  425. INetChannel *pChannel = FindChannel( adr->GetIPNetworkByteOrder() );
  426. if ( pChannel )
  427. {
  428. Msg( "Setting new timeout for ip %d: %d\n", adr->GetIPNetworkByteOrder(), timeout );
  429. pChannel->SetTimeout( timeout );
  430. }
  431. }
  432. //-----------------------------------------------------------------------------
  433. // Purpose: Send a net message to a specific address
  434. //-----------------------------------------------------------------------------
  435. void CMatchmaking::SendMessage( INetMessage *msg, netadr_t *adr, bool bVoice )
  436. {
  437. // Find the matching net channel
  438. INetChannel *pChannel = FindChannel( adr->GetIPNetworkByteOrder() );
  439. if ( pChannel )
  440. {
  441. pChannel->SendNetMsg( *msg, false, bVoice );
  442. if ( !pChannel->Transmit() )
  443. {
  444. Msg( "Transmit failed\n" );
  445. }
  446. }
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose: Send a net message to a specific client
  450. //-----------------------------------------------------------------------------
  451. void CMatchmaking::SendMessage( INetMessage *msg, CClientInfo *pClient, bool bVoice )
  452. {
  453. // Find the matching net channel
  454. INetChannel *pChannel = FindChannel( pClient->m_adr.GetIPNetworkByteOrder() );
  455. if ( pChannel )
  456. {
  457. pChannel->SendNetMsg( *msg, false, bVoice );
  458. if ( !pChannel->Transmit() )
  459. {
  460. Msg( "Transmit failed\n" );
  461. }
  462. }
  463. }
  464. //-----------------------------------------------------------------------------
  465. // Purpose: Send a net message to all remote clients
  466. //-----------------------------------------------------------------------------
  467. void CMatchmaking::SendToRemoteClients( INetMessage *msg, bool bVoice, XUID excludeXUID )
  468. {
  469. for ( int i = 0; i < m_Remote.Count(); ++i )
  470. {
  471. CClientInfo *pInfo = m_Remote[i];
  472. if ( excludeXUID != -1 && pInfo->m_xuids[0] == excludeXUID )
  473. continue;
  474. SendMessage( msg, m_Remote[i], true );
  475. }
  476. }
  477. //-----------------------------------------------------------------------------
  478. // Purpose: Send a heartbeat to a specific client
  479. //-----------------------------------------------------------------------------
  480. bool CMatchmaking::SendHeartbeat( CClientInfo *pClient )
  481. {
  482. if ( pClient->m_adr.GetIPNetworkByteOrder() == 0 )
  483. return false;
  484. // Check for timeout
  485. INetChannel *pChannel = FindChannel( pClient->m_adr.GetIPNetworkByteOrder() );
  486. if ( pChannel )
  487. {
  488. // Msg( "Sending HB\n" );
  489. if ( pChannel->IsTimedOut() )
  490. {
  491. ClientDropped( pClient );
  492. return false;
  493. }
  494. // Send a heartbeat to the client
  495. MM_Heartbeat beat;
  496. pChannel->SendNetMsg( beat );
  497. pChannel->Transmit();
  498. }
  499. return true;
  500. }
  501. //-----------------------------------------------------------------------------
  502. // Purpose: Transmit regular messages to keep the connection alive
  503. //-----------------------------------------------------------------------------
  504. void CMatchmaking::SendHeartbeat()
  505. {
  506. double time = GetTime();
  507. if ( time < m_fNextHeartbeatTime )
  508. return;
  509. m_fNextHeartbeatTime = time + m_fHeartbeatInterval;
  510. if ( m_Session.IsHost() )
  511. {
  512. for ( int i = 0; i < m_Remote.Count(); ++i )
  513. {
  514. SendHeartbeat( m_Remote[i] );
  515. }
  516. }
  517. else
  518. {
  519. SendHeartbeat( &m_Host );
  520. }
  521. }
  522. //-----------------------------------------------------------------------------
  523. // Purpose: Look up a player by name
  524. //-----------------------------------------------------------------------------
  525. static uint64 FindPlayerByName( CClientInfo *pClient, const char *pName )
  526. {
  527. for ( int i = 0; i < XUSER_MAX_COUNT; ++i )
  528. {
  529. if ( pClient->m_xuids[i] && !Q_stricmp( pClient->m_szGamertags[i], pName ) )
  530. {
  531. return pClient->m_xuids[i];
  532. }
  533. }
  534. return 0;
  535. }
  536. //-----------------------------------------------------------------------------
  537. // Purpose: Get an xuid from a CBasePlayer id
  538. //-----------------------------------------------------------------------------
  539. uint64 CMatchmaking::PlayerIdToXuid( int playerId )
  540. {
  541. uint64 ret = 0;
  542. player_info_t info;
  543. if ( engineClient->GetPlayerInfo( playerId, &info ) )
  544. {
  545. // find the client with a matching name
  546. for ( int i = 0; i < m_Remote.Count(); ++i )
  547. {
  548. ret = FindPlayerByName( m_Remote[i], info.name );
  549. if ( ret )
  550. {
  551. break;
  552. }
  553. }
  554. }
  555. if ( !ret )
  556. {
  557. // Try ourselves
  558. ret = FindPlayerByName( &m_Local, info.name );
  559. }
  560. if ( !ret )
  561. {
  562. // Try the host
  563. ret = FindPlayerByName( &m_Host, info.name );
  564. }
  565. return ret;
  566. }
  567. bool CMatchmaking::GameIsActive()
  568. {
  569. return m_CurrentState > MMSTATE_GAME_ACTIVE;
  570. }
  571. bool CMatchmaking::GameIsLocked()
  572. {
  573. return ( m_Session.IsArbitrated() && m_CurrentState > MMSTATE_GAME_LOCKED );
  574. }
  575. bool CMatchmaking::ConnectedToServer()
  576. {
  577. return engineClient->IsConnected();
  578. }
  579. bool CMatchmaking::IsInMigration()
  580. {
  581. return ( m_CurrentState >= MMSTATE_HOSTMIGRATE_STARTINGMIGRATION &&
  582. m_CurrentState <= MMSTATE_HOSTMIGRATE_WAITINGFORHOST );
  583. }
  584. bool CMatchmaking::IsAcceptingConnections()
  585. {
  586. if ( !m_Session.IsHost() ||
  587. m_CurrentState < MMSTATE_ACCEPTING_CONNECTIONS ||
  588. m_CurrentState == MMSTATE_PREGAME ||
  589. m_CurrentState == MMSTATE_LOADING ||
  590. GameIsLocked() )
  591. {
  592. return false;
  593. }
  594. return true;
  595. }
  596. bool CMatchmaking::IsServer()
  597. {
  598. // for now, the host is the server
  599. return m_Session.IsHost();
  600. }
  601. //-----------------------------------------------------------------------------
  602. // Purpose: Helpers to convert between CClientInfo and MM_ClientInfo
  603. //-----------------------------------------------------------------------------
  604. void CMatchmaking::ClientInfoToNetMessage( MM_ClientInfo *pInfo, const CClientInfo *pClient )
  605. {
  606. pInfo->m_id = pClient->m_id;
  607. pInfo->m_xnaddr = pClient->m_xnaddr;
  608. pInfo->m_cPlayers = pClient->m_cPlayers;
  609. pInfo->m_bInvited = pClient->m_bInvited;
  610. Q_memcpy( pInfo->m_xuids, pClient->m_xuids, sizeof( pInfo->m_xuids ) );
  611. Q_memcpy( pInfo->m_cVoiceState, pClient->m_cVoiceState, sizeof( pInfo->m_cVoiceState ) );
  612. Q_memcpy( pInfo->m_iTeam, pClient->m_iTeam, sizeof( pInfo->m_iTeam ) );
  613. Q_memcpy( pInfo->m_iControllers, pClient->m_iControllers, sizeof( pInfo->m_iControllers ) );
  614. Q_memcpy( pInfo->m_szGamertags, pClient->m_szGamertags, sizeof( pInfo->m_szGamertags ) );
  615. }
  616. void CMatchmaking::NetMessageToClientInfo( CClientInfo *pClient, const MM_ClientInfo *pInfo )
  617. {
  618. pClient->m_id = pInfo->m_id;
  619. pClient->m_xnaddr = pInfo->m_xnaddr;
  620. pClient->m_cPlayers = pInfo->m_cPlayers;
  621. pClient->m_bInvited = pInfo->m_bInvited;
  622. #if defined( _X360 )
  623. IN_ADDR winaddr;
  624. XNKID xid = m_Session.GetSessionId();
  625. if ( XNetXnAddrToInAddr( &pClient->m_xnaddr, &xid, &winaddr ) != 0 )
  626. {
  627. Warning( "Error resolving client IP\n" );
  628. }
  629. pClient->m_adr.SetType( NA_IP );
  630. pClient->m_adr.SetIPAndPort( winaddr.S_un.S_addr, PORT_MATCHMAKING );
  631. #endif
  632. Q_memcpy( pClient->m_xuids, pInfo->m_xuids, sizeof( pClient->m_xuids ) );
  633. Q_memcpy( pClient->m_cVoiceState, pInfo->m_cVoiceState, sizeof( pClient->m_cVoiceState ) );
  634. Q_memcpy( pClient->m_iTeam, pInfo->m_iTeam, sizeof( pClient->m_iTeam ) );
  635. Q_memcpy( pClient->m_iControllers, pInfo->m_iControllers, sizeof( pClient->m_iControllers ) );
  636. Q_memcpy( pClient->m_szGamertags, pInfo->m_szGamertags, sizeof( pClient->m_szGamertags ) );
  637. }
  638. //----------------------------------------
  639. //
  640. // Host/Client Shared
  641. //
  642. //----------------------------------------
  643. //-----------------------------------------------------------------------------
  644. // Purpose: Set up the properties of the local client machine
  645. //-----------------------------------------------------------------------------
  646. bool CMatchmaking::InitializeLocalClient( bool bIsHost )
  647. {
  648. Q_memset( &m_Local, 0, sizeof( m_Local ) );
  649. m_Local.m_bInvited = bIsHost;
  650. #if defined( _X360 )
  651. while( XNetGetTitleXnAddr( &m_Local.m_xnaddr ) == XNET_GET_XNADDR_PENDING )
  652. ;
  653. // machine id
  654. if ( 0 != XNetXnAddrToMachineId( &m_Local.m_xnaddr, &m_Local.m_id ) )
  655. {
  656. // User isn't signed in to live, use their xuid instead
  657. XUserGetXUID( XBX_GetPrimaryUserId(), &m_Local.m_id );
  658. }
  659. m_Local.m_cPlayers = 0;
  660. // Set up the players
  661. for ( uint i = 0; i < MAX_PLAYERS_PER_CLIENT; ++i )
  662. {
  663. // We currently only allow one player per console
  664. if ( i != XBX_GetPrimaryUserId() )
  665. {
  666. continue;
  667. }
  668. // xuid
  669. uint ret = XUserGetXUID( i, &m_Local.m_xuids[m_Local.m_cPlayers] );
  670. if ( ret == ERROR_NO_SUCH_USER )
  671. {
  672. continue;
  673. }
  674. else if ( ret != ERROR_SUCCESS )
  675. {
  676. return false;
  677. }
  678. // gamertag
  679. ret = XUserGetName( XBX_GetPrimaryUserId(), m_Local.m_szGamertags[m_Local.m_cPlayers], MAX_PLAYER_NAME_LENGTH );
  680. if ( ret != ERROR_SUCCESS )
  681. {
  682. return false;
  683. }
  684. m_Local.m_szGamertags[m_Local.m_cPlayers][MAX_PLAYER_NAME_LENGTH - 1] = '\0';
  685. // Set the player's name in the game
  686. char szNameCmd[MAX_PLAYER_NAME_LENGTH + 16];
  687. Q_snprintf( szNameCmd, sizeof( szNameCmd ), "name %s", m_Local.m_szGamertags[m_Local.m_cPlayers] );
  688. engineClient->ClientCmd( szNameCmd );
  689. m_Local.m_iControllers[m_Local.m_cPlayers] = i;
  690. m_Local.m_iTeam[m_Local.m_cPlayers] = -1;
  691. m_Local.m_cVoiceState[m_Local.m_cPlayers] = 0;
  692. // number of players on this console
  693. ++m_Local.m_cPlayers;
  694. }
  695. // Source can only support one player per console.
  696. if( m_Local.m_cPlayers > 1 )
  697. {
  698. Warning( "Too many players on this console\n" );
  699. return false;
  700. }
  701. // Set up the host data that gets sent back to searching clients
  702. // By default, the first player is considered the host
  703. Q_strncpy( m_HostData.hostName, m_Local.m_szGamertags[0], sizeof( m_HostData.hostName ) );
  704. m_HostData.gameState = GAMESTATE_INLOBBY;
  705. m_HostData.xuid = m_Local.m_xuids[ XBX_GetPrimaryUserId() ];
  706. #endif
  707. m_bInitialized = true;
  708. return true;
  709. }
  710. //-----------------------------------------------------------------------------
  711. // Purpose: Connection to the game server has been established, so we can
  712. // add the local players to the teams that were setup in the lobby.
  713. //-----------------------------------------------------------------------------
  714. void CMatchmaking::AddLocalPlayersToTeams()
  715. {
  716. if ( !m_bInitialized || XBX_GetPrimaryUserId() == INVALID_USER_ID )
  717. return;
  718. if ( m_Local.m_iTeam[0] == -1 )
  719. return;
  720. // Convert the team number into a team name
  721. char szTeamName[32];
  722. uint id = g_ClientDLL->GetPresenceID( "PROPERTY_TEAM" );
  723. g_ClientDLL->GetPropertyDisplayString( id, m_Local.m_iTeam[0], szTeamName, sizeof( szTeamName ) );
  724. Msg( "Joining team: %s\n", szTeamName );
  725. char cmd[32];
  726. Q_snprintf( cmd, sizeof( cmd ), "jointeam_nomenus %s", szTeamName );
  727. engineClient->ClientCmd( cmd );
  728. }
  729. //-----------------------------------------------------------------------------
  730. // Purpose: Map loading is completed - restore full communication
  731. //-----------------------------------------------------------------------------
  732. void CMatchmaking::OnLevelLoadingFinished()
  733. {
  734. // This functions gets called from some odd places
  735. if ( m_CurrentState != MMSTATE_CONNECTED_TO_SERVER )
  736. return;
  737. // Test code to force a disconnect at end of map load
  738. // if ( !IsServer() )
  739. // {
  740. // char cmd[MAX_PATH];
  741. // Q_snprintf( cmd, sizeof( cmd ), "connect 127.0.0.1\n" );
  742. // Cbuf_AddText( cmd );
  743. // return;
  744. // }
  745. SwitchToState( MMSTATE_INGAME );
  746. MM_Checkpoint msg;
  747. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_LOADING_COMPLETE;
  748. if ( m_Session.IsHost() )
  749. {
  750. if ( !m_Session.IsArbitrated() )
  751. {
  752. // Re-enable response to probes
  753. m_HostData.gameState = GAMESTATE_INPROGRESS;
  754. UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA );
  755. }
  756. // Reset netchannel timeouts for any clients that are also finished loading
  757. for ( int i = 0; i < m_Remote.Count(); ++i )
  758. {
  759. CClientInfo *pClient = m_Remote[i];
  760. if ( pClient->m_bLoaded )
  761. {
  762. // Send a reply and reset the netchannel timeout
  763. SendMessage( &msg, pClient );
  764. SetChannelTimeout( &pClient->m_adr, HEARTBEAT_TIMEOUT );
  765. }
  766. }
  767. }
  768. else
  769. {
  770. // Tell the host we're finished loading
  771. SendMessage( &msg, &m_Host );
  772. }
  773. }
  774. //-----------------------------------------------------------------------------
  775. // Purpose: Process packet from another client
  776. //-----------------------------------------------------------------------------
  777. bool CMatchmaking::ProcessConnectionlessPacket( netpacket_t *pPacket )
  778. {
  779. Assert( pPacket );
  780. bf_read &msg = pPacket->message;
  781. int type = msg.ReadByte();
  782. switch( type )
  783. {
  784. case PTH_SYSTEMLINK_SEARCH:
  785. HandleSystemLinkSearch( pPacket );
  786. break;
  787. case HTP_SYSTEMLINK_REPLY:
  788. HandleSystemLinkReply( pPacket );
  789. break;
  790. case PTH_CONNECT:
  791. HandleJoinRequest( pPacket );
  792. break;
  793. default:
  794. break;
  795. }
  796. return true;
  797. }
  798. //-----------------------------------------------------------------------------
  799. // Purpose: Process an info update about another client
  800. //-----------------------------------------------------------------------------
  801. bool CMatchmaking::ProcessClientInfo( MM_ClientInfo *pInfo )
  802. {
  803. CClientInfo *pClient = NULL;
  804. bool bHost = false;
  805. if ( m_CurrentState == MMSTATE_INITIAL )
  806. {
  807. // Session has been reset, this is a stale message
  808. Msg( "Received MM_ClientInfo with MMSTATE_INITIAL\n" );
  809. return true;
  810. }
  811. if ( pInfo->m_id == m_Local.m_id )
  812. {
  813. if ( pInfo->m_cPlayers == 0 )
  814. {
  815. if ( m_CurrentState != MMSTATE_SESSION_DISCONNECTING )
  816. {
  817. // We've been kicked
  818. KickPlayerFromSession( 0 );
  819. SessionNotification( SESSION_NOTIFY_CLIENT_KICKED );
  820. }
  821. return true;
  822. }
  823. else
  824. {
  825. pClient = &m_Local;
  826. }
  827. }
  828. // Check against our host id
  829. if ( pInfo->m_id == m_Host.m_id )
  830. {
  831. pClient = &m_Host;
  832. bHost = true;
  833. }
  834. else
  835. {
  836. // Look for the client in our remote list
  837. for ( int i = 0; i < m_Remote.Count(); ++i )
  838. {
  839. if ( m_Remote[i]->m_id == pInfo->m_id )
  840. {
  841. pClient = m_Remote[i];
  842. break;
  843. }
  844. }
  845. }
  846. // If we didn't find it, this must be a new client
  847. if ( !pClient && pInfo->m_cPlayers != 0 )
  848. {
  849. Msg( "New client. %s\n", pInfo->ToString() );
  850. pClient = new CClientInfo();
  851. m_Remote.AddToTail( pClient );
  852. // Copy the new client info
  853. NetMessageToClientInfo( pClient, pInfo );
  854. AddPlayersToSession( pClient );
  855. SendPlayerInfoToLobby( pClient );
  856. }
  857. else
  858. {
  859. // We're updating an existing client
  860. if ( pInfo->m_cPlayers )
  861. {
  862. // Cache off the old client info, as pClient gets updated through this function
  863. CClientInfo tempClient = *pClient;
  864. // Check for player changes
  865. if ( Q_memcmp( &tempClient.m_xuids, pInfo->m_xuids, sizeof( tempClient.m_xuids ) ) )
  866. {
  867. // Remove the old players and add the new
  868. RemovePlayersFromSession( pClient );
  869. NetMessageToClientInfo( pClient, pInfo );
  870. AddPlayersToSession( pClient );
  871. }
  872. // Check for team changes
  873. for ( int i = 0; i < pInfo->m_cPlayers; ++i )
  874. {
  875. if ( pInfo->m_iTeam[i] != tempClient.m_iTeam[i] || pInfo->m_cVoiceState[i] != tempClient.m_cVoiceState[i] )
  876. {
  877. // X360TBD: send real "ready" setting, or remove entirely?
  878. EngineVGui()->UpdatePlayerInfo( pInfo->m_xuids[i], pInfo->m_szGamertags[i], pInfo->m_iTeam[i], pInfo->m_cVoiceState[i], GetPlayersNeeded(), bHost );
  879. }
  880. }
  881. // Store the new info
  882. NetMessageToClientInfo( pClient, pInfo );
  883. if ( m_Session.IsHost() )
  884. {
  885. SendToRemoteClients( pInfo );
  886. }
  887. }
  888. else
  889. {
  890. // A client has been dropped
  891. ClientDropped( pClient );
  892. }
  893. }
  894. return true;
  895. }
  896. //-----------------------------------------------------------------------------
  897. // Purpose: A connection was lost - respond accordingly
  898. //-----------------------------------------------------------------------------
  899. void CMatchmaking::ClientDropped( CClientInfo *pClient )
  900. {
  901. if ( !pClient )
  902. {
  903. Warning( "Null client pointer in ClientDropped!\n" );
  904. return;
  905. }
  906. if ( m_CurrentState == MMSTATE_SESSION_CONNECTING )
  907. {
  908. // Not really dropped, we just failed to connect to the host
  909. SessionNotification( SESSION_NOTIFY_CONNECT_NOTAVAILABLE );
  910. return;
  911. }
  912. Warning( "Dropped player: %llu!", pClient->m_id );
  913. // Do this first, before the team assignment gets cleared
  914. RemovePlayersFromSession( pClient );
  915. // Remove all players from the lobby
  916. for ( int i = 0; i < pClient->m_cPlayers; ++i )
  917. {
  918. pClient->m_iTeam[i] = -1;
  919. }
  920. SendPlayerInfoToLobby( pClient );
  921. MarkChannelForRemoval( &pClient->m_adr );
  922. if ( pClient == &m_Host )
  923. {
  924. // The host was lost
  925. if ( m_Session.IsSystemLink() )
  926. {
  927. // Can't migrate system link sessions
  928. SessionNotification( SESSION_NOTIFY_LOST_HOST );
  929. }
  930. else
  931. {
  932. // X360TBD: Migration still doesn't work correctly
  933. SessionNotification( SESSION_NOTIFY_LOST_HOST );
  934. /*
  935. // Start migrating
  936. if ( !IsInMigration() )
  937. {
  938. m_PreMigrateState = m_CurrentState;
  939. StartHostMigration();
  940. }
  941. */
  942. }
  943. }
  944. else
  945. {
  946. if ( m_Session.IsHost() )
  947. {
  948. // Send a disconnect ack back to the client
  949. MM_Checkpoint msg;
  950. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_SESSION_DISCONNECT;
  951. SendMessage( &msg, &pClient->m_adr );
  952. // Tell everyone else this client is gone
  953. MM_ClientInfo droppedPlayer;
  954. ClientInfoToNetMessage( &droppedPlayer, pClient );
  955. droppedPlayer.m_cPlayers = 0;
  956. SendToRemoteClients( &droppedPlayer );
  957. for ( int i = 0; i < sv.GetClientCount(); i++ )
  958. {
  959. IClient *pIClient = sv.GetClient(i);
  960. bool bFound = false;
  961. if ( pIClient )
  962. {
  963. for ( int j = 0; j < pClient->m_cPlayers; ++j )
  964. {
  965. if ( pClient->m_xuids[j] == 0 )
  966. continue;
  967. if ( Q_stricmp( pIClient->GetClientName(), pClient->m_szGamertags[j] ) == 0 )
  968. {
  969. bFound = true;
  970. pIClient->Disconnect( "Timed Out" );
  971. break;
  972. }
  973. }
  974. }
  975. if ( bFound == true )
  976. break;
  977. }
  978. }
  979. }
  980. if ( m_Remote.FindAndRemove( pClient ) )
  981. {
  982. delete pClient;
  983. }
  984. }
  985. //-----------------------------------------------------------------------------
  986. // Purpose: A player is leaving the session
  987. //-----------------------------------------------------------------------------
  988. void CMatchmaking::PerformDisconnect()
  989. {
  990. if ( m_CurrentState == MMSTATE_SESSION_DISCONNECTING )
  991. {
  992. if ( ConnectedToServer() )
  993. {
  994. engineClient->ExecuteClientCmd( "disconnect" );
  995. EngineVGui()->ActivateGameUI();
  996. }
  997. if ( m_bEnteredLobby )
  998. {
  999. EngineVGui()->SessionNotification( SESSION_NOTIFY_WELCOME );
  1000. }
  1001. SwitchToState( MMSTATE_INITIAL );
  1002. }
  1003. }
  1004. //-----------------------------------------------------------------------------
  1005. // Purpose: A player is leaving the session
  1006. //-----------------------------------------------------------------------------
  1007. void CMatchmaking::KickPlayerFromSession( uint64 id )
  1008. {
  1009. MM_ClientInfo droppedPlayer;
  1010. if ( m_Local.m_xuids[0] == id || id == 0 )
  1011. {
  1012. // We've been kicked, or voluntarily left the session
  1013. ClientInfoToNetMessage( &droppedPlayer, &m_Local );
  1014. droppedPlayer.m_cPlayers = 0;
  1015. if ( m_Session.IsHost() )
  1016. {
  1017. // Tell all the clients
  1018. SendToRemoteClients( &droppedPlayer );
  1019. }
  1020. else
  1021. {
  1022. // tell the host to drop us
  1023. SendMessage( &droppedPlayer, &m_Host );
  1024. }
  1025. // Prepare to close the session and reset
  1026. SwitchToState( MMSTATE_SESSION_DISCONNECTING );
  1027. }
  1028. else if ( m_Session.IsHost() )
  1029. {
  1030. // Host wants to kick a client
  1031. for ( int i = 0; i < m_Remote.Count(); ++i )
  1032. {
  1033. CClientInfo *pClient = m_Remote[i];
  1034. for ( int j = 0; j < pClient->m_cPlayers; ++j )
  1035. {
  1036. if ( pClient->m_xuids[j] == id )
  1037. {
  1038. ClientInfoToNetMessage( &droppedPlayer, pClient );
  1039. droppedPlayer.m_cPlayers = 0;
  1040. SendMessage( &droppedPlayer, pClient );
  1041. return;
  1042. }
  1043. }
  1044. }
  1045. }
  1046. }
  1047. //-----------------------------------------------------------------------------
  1048. // Purpose: Add players to the session
  1049. //-----------------------------------------------------------------------------
  1050. void CMatchmaking::AddPlayersToSession( CClientInfo *pClient )
  1051. {
  1052. bool bIsLocal = false;
  1053. if ( &m_Local == pClient )
  1054. {
  1055. m_Session.JoinLocal( pClient );
  1056. bIsLocal = true;
  1057. }
  1058. else
  1059. {
  1060. m_Session.JoinRemote( pClient );
  1061. }
  1062. #if defined ( _X360 )
  1063. if ( Audio_GetXVoice() )
  1064. {
  1065. Audio_GetXVoice()->AddPlayerToVoiceList( pClient, bIsLocal );
  1066. if ( bIsLocal )
  1067. {
  1068. m_bCreatedLocalTalker = true;
  1069. }
  1070. }
  1071. #endif
  1072. UpdateMuteList();
  1073. }
  1074. //-----------------------------------------------------------------------------
  1075. // Purpose: Remove players from the session
  1076. //-----------------------------------------------------------------------------
  1077. void CMatchmaking::RemovePlayersFromSession( CClientInfo *pClient )
  1078. {
  1079. if ( !pClient->m_cPlayers )
  1080. return;
  1081. bool bIsLocal = false;
  1082. if ( &m_Local == pClient )
  1083. {
  1084. m_Session.RemoveLocal( pClient );
  1085. bIsLocal = true;
  1086. }
  1087. else
  1088. {
  1089. m_Session.RemoveRemote( pClient );
  1090. }
  1091. #if defined ( _X360 )
  1092. if ( Audio_GetXVoice() )
  1093. {
  1094. Audio_GetXVoice()->RemovePlayerFromVoiceList( pClient, bIsLocal );
  1095. }
  1096. #endif
  1097. UpdateMuteList();
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. // Purpose: Check if a client is muted
  1101. //-----------------------------------------------------------------------------
  1102. bool CMatchmaking::IsPlayerMuted( int iUserId, XUID playerId )
  1103. {
  1104. for ( int i = 0; i < MAX_PLAYERS; ++i )
  1105. {
  1106. if ( m_Mutelist[iUserId][i] == playerId )
  1107. {
  1108. return true;
  1109. }
  1110. }
  1111. if ( m_MutedBy[iUserId].HasElement( playerId ) )
  1112. return true;
  1113. return false;
  1114. }
  1115. //-----------------------------------------------------------------------------
  1116. // Purpose: Determine which clients should be muted for the local client
  1117. //-----------------------------------------------------------------------------
  1118. void CMatchmaking::UpdateMuteList()
  1119. {
  1120. // Process our mute list
  1121. MM_Mutelist msg;
  1122. GenerateMutelist( &msg );
  1123. // Send that to everyone else
  1124. if ( !m_Session.IsHost() )
  1125. {
  1126. SendMessage( &msg, &m_Host );
  1127. }
  1128. else
  1129. {
  1130. ProcessMutelist( &msg );
  1131. }
  1132. }
  1133. void Con_PrintTalkers( const CCommand &args )
  1134. {
  1135. g_pMatchmaking->PrintVoiceStatus();
  1136. }
  1137. void CMatchmaking::PrintVoiceStatus( void )
  1138. {
  1139. #ifdef _X360
  1140. int iRemoteClient = 0;
  1141. int numRemoteTalkers;
  1142. XUID remoteTalkers[MAX_PLAYERS];
  1143. Audio_GetXVoice()->GetRemoteTalkers( &numRemoteTalkers, remoteTalkers );
  1144. CClientInfo *pClient = &m_Local;
  1145. if ( m_Session.IsHost() == false )
  1146. {
  1147. pClient = &m_Host;
  1148. }
  1149. Msg( "Num Remote Talkers: %d\n", numRemoteTalkers );
  1150. bool bFound = false;
  1151. while ( pClient )
  1152. {
  1153. if ( pClient != &m_Local )
  1154. {
  1155. for ( int iRemote = 0; iRemote < numRemoteTalkers; iRemote++ )
  1156. {
  1157. if ( pClient->m_xuids[0] == remoteTalkers[iRemote] )
  1158. {
  1159. bFound = true;
  1160. break;
  1161. }
  1162. }
  1163. if ( bFound == true )
  1164. {
  1165. Msg( "Found a Talker: %s\n", pClient->m_szGamertags[0] );
  1166. }
  1167. else
  1168. {
  1169. Msg( "ALERT!!! %s not in Talker list\n", pClient->m_szGamertags[0] );
  1170. }
  1171. }
  1172. if ( iRemoteClient < m_Remote.Count() )
  1173. {
  1174. pClient = m_Remote[iRemoteClient];
  1175. iRemoteClient++;
  1176. }
  1177. else
  1178. {
  1179. pClient = NULL;
  1180. }
  1181. }
  1182. #endif
  1183. }
  1184. static ConCommand voice_printtalkers( "voice_printtalkers", Con_PrintTalkers, "voice debug.", FCVAR_DONTRECORD );
  1185. //-----------------------------------------------------------------------------
  1186. // Purpose: Update a client's mute list
  1187. //-----------------------------------------------------------------------------
  1188. void CMatchmaking::GenerateMutelist( MM_Mutelist *pMsg )
  1189. {
  1190. #if defined( _X360 )
  1191. // Get our remote talker list
  1192. if ( !Audio_GetXVoice() )
  1193. return;
  1194. int numRemoteTalkers;
  1195. XUID remoteTalkers[MAX_PLAYERS];
  1196. Audio_GetXVoice()->GetRemoteTalkers( &numRemoteTalkers, remoteTalkers );
  1197. pMsg->m_cPlayers = 0;
  1198. // Loop through local players and update mutes
  1199. for ( int iLocal = 0; iLocal < m_Local.m_cPlayers; ++iLocal )
  1200. {
  1201. pMsg->m_cMuted[iLocal] = 0;
  1202. pMsg->m_xuid[pMsg->m_cPlayers] = m_Local.m_xuids[iLocal];
  1203. for ( int iRemote = 0; iRemote < numRemoteTalkers; ++iRemote )
  1204. {
  1205. BOOL bIsMuted = false;
  1206. DWORD ret = XUserMuteListQuery( m_Local.m_iControllers[iLocal], remoteTalkers[iRemote], &bIsMuted );
  1207. if( ERROR_SUCCESS != ret )
  1208. {
  1209. Warning( "Warning: XUserMuteListQuery() returned 0x%08x for user %d\n", ret, iLocal );
  1210. }
  1211. if( bIsMuted )
  1212. {
  1213. pMsg->m_Muted[pMsg->m_cPlayers].AddToTail( remoteTalkers[iRemote ] );
  1214. ++pMsg->m_cMuted[pMsg->m_cPlayers];
  1215. }
  1216. else
  1217. {
  1218. bIsMuted = m_MutedBy[iLocal].HasElement( remoteTalkers[iRemote] );
  1219. }
  1220. if( bIsMuted )
  1221. {
  1222. Audio_GetXVoice()->SetPlaybackPriority( remoteTalkers[iRemote], m_Local.m_iControllers[iLocal], XHV_PLAYBACK_PRIORITY_NEVER );
  1223. }
  1224. else
  1225. {
  1226. Audio_GetXVoice()->SetPlaybackPriority( remoteTalkers[iRemote], m_Local.m_iControllers[iLocal], XHV_PLAYBACK_PRIORITY_MAX );
  1227. }
  1228. }
  1229. pMsg->m_cRemoteTalkers[pMsg->m_cPlayers] = m_MutedBy[pMsg->m_cPlayers].Count();
  1230. ++pMsg->m_cPlayers;
  1231. }
  1232. #endif
  1233. }
  1234. //-----------------------------------------------------------------------------
  1235. // Purpose: Handle the mutelist from another client
  1236. //-----------------------------------------------------------------------------
  1237. bool CMatchmaking::ProcessMutelist( MM_Mutelist *pMsg )
  1238. {
  1239. #if defined( _X360 )
  1240. // local players
  1241. for( int i = 0; i < m_Local.m_cPlayers; ++i )
  1242. {
  1243. // remote players
  1244. for( int j = 0; j < pMsg->m_cPlayers; ++j )
  1245. {
  1246. m_MutedBy[i].FindAndRemove( pMsg->m_xuid[j] );
  1247. // players muted by remote player
  1248. for( int k = 0; k < pMsg->m_cMuted[j]; ++k )
  1249. {
  1250. if( m_Local.m_xuids[i] == pMsg->m_Muted[j][k] )
  1251. {
  1252. m_MutedBy[i].AddToTail( pMsg->m_xuid[j] );
  1253. }
  1254. }
  1255. BOOL bIsMuted = m_MutedBy[i].HasElement( pMsg->m_xuid[j] );
  1256. if ( !bIsMuted )
  1257. {
  1258. XUserMuteListQuery( m_Local.m_iControllers[i], pMsg->m_xuid[j], &bIsMuted );
  1259. }
  1260. if( bIsMuted )
  1261. {
  1262. Audio_GetXVoice()->SetPlaybackPriority( pMsg->m_xuid[j], m_Local.m_iControllers[i], XHV_PLAYBACK_PRIORITY_NEVER );
  1263. }
  1264. else
  1265. {
  1266. Audio_GetXVoice()->SetPlaybackPriority( pMsg->m_xuid[j], m_Local.m_iControllers[i], XHV_PLAYBACK_PRIORITY_MAX );
  1267. }
  1268. }
  1269. }
  1270. if ( m_Session.IsHost() )
  1271. {
  1272. // Pass this along to everyone else
  1273. SendToRemoteClients( pMsg );
  1274. }
  1275. #endif
  1276. return true;
  1277. }
  1278. //-----------------------------------------------------------------------------
  1279. // Purpose: A client is changing to another team
  1280. //-----------------------------------------------------------------------------
  1281. void CMatchmaking::ChangeTeam( const char *pTeamName )
  1282. {
  1283. if ( !pTeamName )
  1284. {
  1285. // Automatic switch to next team
  1286. if ( m_Session.IsHost() )
  1287. {
  1288. if ( m_CurrentState == MMSTATE_ACCEPTING_CONNECTIONS )
  1289. {
  1290. // Put ourselves on another team and
  1291. // tell the other players
  1292. SwitchToNextOpenTeam( &m_Local );
  1293. }
  1294. }
  1295. else
  1296. {
  1297. // Send a request to the host
  1298. MM_Checkpoint msg;
  1299. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_CHANGETEAM;
  1300. SendMessage( &msg, &m_Host );
  1301. }
  1302. }
  1303. else
  1304. {
  1305. // Find a team name that matches, and tell everyone our new team number
  1306. char szTeamName[32];
  1307. uint id = g_ClientDLL->GetPresenceID( "PROPERTY_TEAM" );
  1308. for ( int iTeam = 0; iTeam < m_nTotalTeams; ++iTeam )
  1309. {
  1310. g_ClientDLL->GetPropertyDisplayString( id, iTeam, szTeamName, sizeof( szTeamName ) );
  1311. if ( !Q_stricmp( szTeamName, pTeamName ) )
  1312. {
  1313. bool bChanged = false;
  1314. MM_ClientInfo info;
  1315. ClientInfoToNetMessage( &info, &m_Local );
  1316. for ( int i = 0; i < m_Local.m_cPlayers; ++i )
  1317. {
  1318. if ( info.m_iTeam[i] != iTeam )
  1319. {
  1320. bChanged = true;
  1321. }
  1322. info.m_iTeam[i] = iTeam;
  1323. }
  1324. if ( !bChanged )
  1325. {
  1326. return;
  1327. }
  1328. if ( m_Session.IsHost() )
  1329. {
  1330. ProcessClientInfo( &info );
  1331. }
  1332. else
  1333. {
  1334. SendMessage( &info, &m_Host );
  1335. }
  1336. break;
  1337. }
  1338. }
  1339. }
  1340. }
  1341. //-----------------------------------------------------------------------------
  1342. // Purpose: Handle various matchmaking checkpoint messages
  1343. //-----------------------------------------------------------------------------
  1344. bool CMatchmaking::ProcessCheckpoint( MM_Checkpoint *pMsg )
  1345. {
  1346. switch( pMsg->m_Checkpoint )
  1347. {
  1348. case MM_Checkpoint::CHECKPOINT_CHANGETEAM:
  1349. if ( m_Session.IsHost() )
  1350. {
  1351. // if the countdown has started, deny
  1352. if ( m_CurrentState == MMSTATE_ACCEPTING_CONNECTIONS )
  1353. {
  1354. netadr_t adr = pMsg->GetNetChannel()->GetRemoteAddress();
  1355. CClientInfo *pClient = FindClient( &adr );
  1356. if ( pClient )
  1357. {
  1358. SwitchToNextOpenTeam( pClient );
  1359. }
  1360. }
  1361. }
  1362. break;
  1363. case MM_Checkpoint::CHECKPOINT_PREGAME:
  1364. if ( m_Session.IsArbitrated() && !m_Session.IsHost() )
  1365. {
  1366. m_Session.RegisterForArbitration();
  1367. }
  1368. // Start the countdown timer to map load
  1369. SwitchToState( MMSTATE_PREGAME );
  1370. break;
  1371. case MM_Checkpoint::CHECKPOINT_GAME_LOBBY:
  1372. // returning to game lobby, pregame canceled
  1373. // reset the countdown
  1374. SessionNotification( SESSION_NOTIFY_COUNTDOWN, -1 );
  1375. if ( m_Session.IsHost() )
  1376. {
  1377. SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS );
  1378. }
  1379. else
  1380. {
  1381. SwitchToState( MMSTATE_SESSION_CONNECTED );
  1382. }
  1383. break;
  1384. case MM_Checkpoint::CHECKPOINT_LOADING_COMPLETE:
  1385. if ( m_Session.IsHost() )
  1386. {
  1387. // Mark this client as loaded
  1388. netadr_t adr = pMsg->GetNetChannel()->GetRemoteAddress();
  1389. CClientInfo *pClient = FindClient( &adr );
  1390. if ( pClient )
  1391. {
  1392. pClient->m_bLoaded = true;
  1393. if ( GameIsActive() )
  1394. {
  1395. // Send a reply and reset the netchannel timeout
  1396. SendMessage( pMsg, pClient );
  1397. SetChannelTimeout( &pClient->m_adr, HEARTBEAT_TIMEOUT );
  1398. }
  1399. }
  1400. }
  1401. else
  1402. {
  1403. // The host is also loaded, so reset the netchannel timeout
  1404. SetChannelTimeout( &m_Host.m_adr, HEARTBEAT_TIMEOUT );
  1405. }
  1406. break;
  1407. case MM_Checkpoint::CHECKPOINT_CONNECT:
  1408. // If we're already connected or in the game, don't connect again.
  1409. if ( m_CurrentState == MMSTATE_CONNECTED_TO_SERVER ||
  1410. m_CurrentState == MMSTATE_INGAME )
  1411. {
  1412. break;
  1413. }
  1414. if ( m_Session.IsHost() )
  1415. {
  1416. // Send the message to everyone
  1417. SendToRemoteClients( pMsg );
  1418. }
  1419. else
  1420. {
  1421. // The host is asking us to connect to the game server
  1422. if ( m_CurrentState != MMSTATE_LOADING )
  1423. {
  1424. // Set the longer timeout for loading
  1425. SetChannelTimeout( &m_Host.m_adr, HEARTBEAT_TIMEOUT_LOADING );
  1426. SwitchToState( MMSTATE_LOADING );
  1427. }
  1428. }
  1429. // Make sure we're not preventing full startup
  1430. SetPreventFullServerStartup( false, "CHECKPOINT_CONNECT\n" );
  1431. if ( !IsServer() )
  1432. {
  1433. char cmd[MAX_PATH];
  1434. Q_snprintf( cmd, sizeof( cmd ), "connect %d.%d.%d.%d", m_Host.m_adr.ip[0], m_Host.m_adr.ip[1], m_Host.m_adr.ip[2], m_Host.m_adr.ip[3] );
  1435. Cbuf_AddText( cmd );
  1436. SessionNotification( SESSION_NOTIFY_CONNECTED_TOSERVER );
  1437. }
  1438. break;
  1439. case MM_Checkpoint::CHECKPOINT_SESSION_DISCONNECT:
  1440. PerformDisconnect();
  1441. break;
  1442. case MM_Checkpoint::CHECKPOINT_REPORT_STATS:
  1443. // Start stats reporting
  1444. g_ClientDLL->StartStatsReporting( m_Session.GetSessionHandle(), m_Session.IsArbitrated() );
  1445. m_Local.m_bReportedStats = true;
  1446. m_fHeartbeatInterval = HEARTBEAT_INTERVAL_SHORT;
  1447. SwitchToState( MMSTATE_REPORTING_STATS );
  1448. // Host needs to wait for clients to finish reporting
  1449. if ( !m_Session.IsHost() )
  1450. {
  1451. EndStatsReporting();
  1452. }
  1453. break;
  1454. case MM_Checkpoint::CHECKPOINT_REPORTING_COMPLETE:
  1455. {
  1456. // Mark this client as finished reporting stats
  1457. bool bFinished = false;
  1458. netadr_t adr = pMsg->GetNetChannel()->GetRemoteAddress();
  1459. for ( int i = 0; i < m_Remote.Count(); ++i )
  1460. {
  1461. CClientInfo *pClient = m_Remote[i];
  1462. if ( pClient->m_adr.CompareAdr( adr ) )
  1463. {
  1464. pClient->m_bReportedStats = true;
  1465. }
  1466. if ( !pClient->m_bReportedStats )
  1467. {
  1468. bFinished = false;
  1469. }
  1470. }
  1471. if ( bFinished && m_Local.m_bReportedStats )
  1472. {
  1473. EndStatsReporting();
  1474. }
  1475. }
  1476. break;
  1477. case MM_Checkpoint::CHECKPOINT_POSTGAME:
  1478. g_pXboxSystem->SessionEnd( m_Session.GetSessionHandle(), false );
  1479. engineClient->ClientCmd( "disconnect" );
  1480. EngineVGui()->ActivateGameUI();
  1481. if ( m_Session.IsArbitrated() )
  1482. {
  1483. // Tell gameui to return to the main menu
  1484. SessionNotification( SESSION_NOTIFY_ENDGAME_RANKED );
  1485. SwitchToState( MMSTATE_INITIAL );
  1486. }
  1487. else
  1488. {
  1489. if ( m_Session.IsHost() )
  1490. {
  1491. // Make ourselves available to queries again
  1492. m_HostData.gameState = GAMESTATE_INLOBBY;
  1493. UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA );
  1494. }
  1495. // Tell gameui to activate the lobby
  1496. SessionNotification( m_Session.IsHost() ? SESSION_NOTIFY_ENDGAME_HOST : SESSION_NOTIFY_ENDGAME_CLIENT );
  1497. // Fill the lobby with all of the clients
  1498. if ( !m_Session.IsHost() )
  1499. {
  1500. SendPlayerInfoToLobby( &m_Host, m_nHostOwnerId );
  1501. SendPlayerInfoToLobby( &m_Local );
  1502. }
  1503. else
  1504. {
  1505. SendPlayerInfoToLobby( &m_Local, 0 );
  1506. }
  1507. for ( int i = 0; i < m_Remote.Count(); ++i )
  1508. {
  1509. SendPlayerInfoToLobby( m_Remote[i] );
  1510. }
  1511. if ( m_Session.IsHost() )
  1512. {
  1513. SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS );
  1514. }
  1515. else
  1516. {
  1517. SwitchToState( MMSTATE_SESSION_CONNECTED );
  1518. }
  1519. }
  1520. break;
  1521. }
  1522. return true;
  1523. }
  1524. //-----------------------------------------------------------------------------
  1525. // Purpose: Stop any asynchronous operation that is currently running
  1526. //-----------------------------------------------------------------------------
  1527. void CMatchmaking::CancelCurrentOperation()
  1528. {
  1529. switch( m_CurrentState )
  1530. {
  1531. case MMSTATE_CREATING:
  1532. m_Session.CancelCreateSession();
  1533. break;
  1534. case MMSTATE_SEARCHING:
  1535. CancelSearch();
  1536. break;
  1537. case MMSTATE_WAITING_QOS:
  1538. CancelQosLookup();
  1539. break;
  1540. case MMSTATE_SESSION_CONNECTING:
  1541. break;
  1542. default:
  1543. break;
  1544. }
  1545. }
  1546. //-----------------------------------------------------------------------------
  1547. // Purpose: Send player info to be displayed in the session lobby
  1548. //-----------------------------------------------------------------------------
  1549. void CMatchmaking::SendPlayerInfoToLobby( CClientInfo *pClient, int iHostIdx )
  1550. {
  1551. for ( int i = 0; i < pClient->m_cPlayers; ++i )
  1552. {
  1553. EngineVGui()->UpdatePlayerInfo( pClient->m_xuids[i], pClient->m_szGamertags[i], pClient->m_iTeam[i], pClient->m_cVoiceState[i], GetPlayersNeeded(), iHostIdx == i );
  1554. }
  1555. }
  1556. //-----------------------------------------------------------------------------
  1557. // Purpose: Update the start game countdown timer
  1558. //-----------------------------------------------------------------------------
  1559. void CMatchmaking::UpdatePregame()
  1560. {
  1561. int elapsedTime = GetTime() - m_fCountdownStartTime;
  1562. if ( elapsedTime > REGISTRATION_MAXWAITTIME )
  1563. {
  1564. // Check the registration timer.
  1565. if ( m_Session.IsHost() && m_Session.IsArbitrated() && !m_Local.m_bRegistered )
  1566. {
  1567. // Time's up, register ourselves
  1568. m_Local.m_bRegistered = true;
  1569. m_Session.RegisterForArbitration();
  1570. }
  1571. }
  1572. // Check the countdown timer. When it's zero, start the game.
  1573. if ( elapsedTime < STARTGAME_COUNTDOWN )
  1574. {
  1575. SessionNotification( SESSION_NOTIFY_COUNTDOWN, STARTGAME_COUNTDOWN - elapsedTime );
  1576. return;
  1577. }
  1578. // Send the zero count
  1579. SessionNotification( SESSION_NOTIFY_COUNTDOWN, 0 );
  1580. // Set a longer timeout for loading
  1581. if ( m_Session.IsHost() )
  1582. {
  1583. for ( int i = 0; i < m_Remote.Count(); ++i )
  1584. {
  1585. SetChannelTimeout( &m_Remote[i]->m_adr, HEARTBEAT_TIMEOUT_LOADING );
  1586. }
  1587. // Set commentary & cheats off
  1588. ConVarRef commentary( "commentary" );
  1589. commentary.SetValue( false );
  1590. ConVarRef sv_cheats( "sv_cheats" );
  1591. sv_cheats.SetValue( 0 );
  1592. }
  1593. else
  1594. {
  1595. SetChannelTimeout( &m_Host.m_adr, HEARTBEAT_TIMEOUT_LOADING );
  1596. }
  1597. g_pXboxSystem->SessionStart( m_Session.GetSessionHandle(), 0, false );
  1598. if ( !IsServer() )
  1599. {
  1600. SetPreventFullServerStartup( true, "SESSION_NOTIFY_COUNTDOWN == 0 and not the host\n" );
  1601. }
  1602. SwitchToState( MMSTATE_LOADING );
  1603. }
  1604. //-----------------------------------------------------------------------------
  1605. // Purpose: Receive notifications from the session
  1606. //-----------------------------------------------------------------------------
  1607. void CMatchmaking::SessionNotification( const SESSION_NOTIFY notification, const int param )
  1608. {
  1609. // Notify GameUI
  1610. EngineVGui()->SessionNotification( notification, param );
  1611. switch( notification )
  1612. {
  1613. case SESSION_NOTIFY_CREATED_HOST:
  1614. m_bEnteredLobby = true;
  1615. OnHostSessionCreated();
  1616. break;
  1617. case SESSION_NOTIFY_CREATED_CLIENT:
  1618. Msg( "Client: CreateSession successful\n" );
  1619. // Session has been created according to the advertised specifications.
  1620. // Now send a connection request to the session host.
  1621. SwitchToState( MMSTATE_SESSION_CONNECTING );
  1622. break;
  1623. case SESSION_NOFIFY_MODIFYING_SESSION:
  1624. SwitchToState( MMSTATE_MODIFYING );
  1625. break;
  1626. case SESSION_NOTIFY_MODIFYING_COMPLETED_HOST:
  1627. SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS );
  1628. break;
  1629. case SESSION_NOTIFY_MODIFYING_COMPLETED_CLIENT:
  1630. SwitchToState( MMSTATE_SESSION_CONNECTED );
  1631. break;
  1632. case SESSION_NOTIFY_SEARCH_COMPLETED:
  1633. // Waiting for the player to choose a session
  1634. SwitchToState( MMSTATE_BROWSING );
  1635. break;
  1636. case SESSION_NOTIFY_CONNECTED_TOSESSION:
  1637. m_bEnteredLobby = true;
  1638. SwitchToState( MMSTATE_SESSION_CONNECTED );
  1639. break;
  1640. case SESSION_NOTIFY_CONNECTED_TOSERVER:
  1641. SwitchToState( MMSTATE_CONNECTED_TO_SERVER );
  1642. break;
  1643. case SESSION_NOTIFY_MIGRATION_COMPLETED:
  1644. if ( !m_Session.IsHost() )
  1645. {
  1646. // Finished
  1647. EndMigration();
  1648. }
  1649. else
  1650. {
  1651. // Get ready to send join requests too peers
  1652. for ( int i = 0; i < m_Remote.Count(); ++i )
  1653. {
  1654. m_Remote[i]->m_bMigrated = false;
  1655. AddRemoteChannel( &m_Remote[i]->m_adr );
  1656. }
  1657. m_nSendCount = 0;
  1658. m_fSendTimer = 0;
  1659. SwitchToState( MMSTATE_HOSTMIGRATE_WAITINGFORCLIENTS );
  1660. }
  1661. break;
  1662. case SESSION_NOTIFY_FAIL_MIGRATE:
  1663. // X360TBD: How to handle this error?
  1664. Warning( "Migrate Failed\n" );
  1665. break;
  1666. case SESSION_NOTIFY_REGISTER_COMPLETED:
  1667. if( !m_Session.IsHost() )
  1668. {
  1669. // Tell the host we're registered
  1670. MM_RegisterResponse msg;
  1671. SendMessage( &msg, &m_Host.m_adr );
  1672. }
  1673. else
  1674. {
  1675. // Process the results of registration
  1676. ProcessRegistrationResults();
  1677. }
  1678. break;
  1679. case SESSION_NOTIFY_ENDGAME_HOST:
  1680. case SESSION_NOTIFY_ENDGAME_CLIENT:
  1681. m_fHeartbeatInterval = HEARTBEAT_INTERVAL_SHORT;
  1682. break;
  1683. case SESSION_NOTIFY_LOST_HOST:
  1684. case SESSION_NOTIFY_LOST_SERVER:
  1685. SwitchToState( MMSTATE_SESSION_DISCONNECTING );
  1686. break;
  1687. case SESSION_NOTIFY_FAIL_REGISTER:
  1688. case SESSION_NOTIFY_FAIL_CREATE:
  1689. case SESSION_NOTIFY_FAIL_SEARCH:
  1690. case SESSION_NOTIFY_CONNECT_SESSIONFULL:
  1691. case SESSION_NOTIFY_CONNECT_NOTAVAILABLE:
  1692. case SESSION_NOTIFY_CONNECT_FAILED:
  1693. // Reset the session
  1694. SwitchToState( MMSTATE_INITIAL );
  1695. break;
  1696. default:
  1697. break;
  1698. }
  1699. }
  1700. //-----------------------------------------------------------------------------
  1701. // Purpose: Switch between matchmaking states
  1702. //-----------------------------------------------------------------------------
  1703. void CMatchmaking::SwitchToState( int newState )
  1704. {
  1705. // Clean up from the previous state
  1706. switch( m_CurrentState )
  1707. {
  1708. case MMSTATE_INITIAL:
  1709. break;
  1710. case MMSTATE_BROWSING:
  1711. // Clean up the search results
  1712. ClearSearchResults();
  1713. break;
  1714. default:
  1715. break;
  1716. }
  1717. // Initialize the next state
  1718. switch( newState )
  1719. {
  1720. case MMSTATE_INITIAL:
  1721. m_bCleanup = true;
  1722. break;
  1723. case MMSTATE_CREATING:
  1724. case MMSTATE_SEARCHING:
  1725. case MMSTATE_ACCEPTING_CONNECTIONS:
  1726. break;
  1727. case MMSTATE_MODIFYING:
  1728. m_fSendTimer = GetTime();
  1729. break;
  1730. case MMSTATE_WAITING_QOS:
  1731. m_fWaitTimer = GetTime();
  1732. break;
  1733. case MMSTATE_SESSION_CONNECTING:
  1734. ConnectToHost();
  1735. break;
  1736. case MMSTATE_PREGAME:
  1737. m_fCountdownStartTime = GetTime();
  1738. break;
  1739. case MMSTATE_LOADING:
  1740. m_fHeartbeatInterval = HEARTBEAT_INTERVAL_LONG;
  1741. break;
  1742. case MMSTATE_SESSION_DISCONNECTING:
  1743. m_fWaitTimer = GetTime();
  1744. break;
  1745. case MMSTATE_REPORTING_STATS:
  1746. m_fWaitTimer = GetTime();
  1747. break;
  1748. default:
  1749. break;
  1750. }
  1751. m_CurrentState = newState;
  1752. }
  1753. void CMatchmaking::UpdateVoiceStatus( void )
  1754. {
  1755. #if defined( _X360 )
  1756. if ( m_flVoiceBlinkTime > GetTime() )
  1757. return;
  1758. m_flVoiceBlinkTime = GetTime() + VOICE_ICON_BLINK_TIME;
  1759. CClientInfo *pClient = &m_Local;
  1760. bool bIsHost = m_Session.IsHost();
  1761. bool bShouldSendInfo = false;
  1762. if ( pClient )
  1763. {
  1764. for ( int i = 0; i < pClient->m_cPlayers; ++i )
  1765. {
  1766. if ( pClient->m_xuids[i] == 0 )
  1767. continue;
  1768. byte cOldVoiceState = pClient->m_cVoiceState[i];
  1769. if ( Audio_GetXVoice()->IsHeadsetPresent( pClient->m_iControllers[i] ) == false )
  1770. {
  1771. pClient->m_cVoiceState[i] = VOICE_STATUS_OFF;
  1772. }
  1773. else
  1774. {
  1775. if ( Audio_GetXVoice()->IsPlayerTalking( pClient->m_xuids[i], true ) )
  1776. {
  1777. pClient->m_cVoiceState[i] = VOICE_STATUS_TALKING;
  1778. }
  1779. else
  1780. {
  1781. pClient->m_cVoiceState[i] = VOICE_STATUS_IDLE;
  1782. }
  1783. }
  1784. if ( cOldVoiceState != pClient->m_cVoiceState[i] )
  1785. {
  1786. bShouldSendInfo = true;
  1787. }
  1788. if ( bShouldSendInfo == true )
  1789. {
  1790. EngineVGui()->UpdatePlayerInfo( pClient->m_xuids[i], pClient->m_szGamertags[i], pClient->m_iTeam[i], pClient->m_cVoiceState[i], GetPlayersNeeded(), bIsHost );
  1791. }
  1792. }
  1793. if ( bShouldSendInfo )
  1794. {
  1795. MM_ClientInfo info;
  1796. ClientInfoToNetMessage( &info, pClient );
  1797. if ( bIsHost == true )
  1798. {
  1799. // Tell all the clients
  1800. ProcessClientInfo( &info );
  1801. }
  1802. else
  1803. {
  1804. // Tell all the clients
  1805. SendMessage( &info, &m_Host );
  1806. }
  1807. }
  1808. }
  1809. #endif
  1810. }
  1811. //-----------------------------------------------------------------------------
  1812. // Update matchmaking and any active session
  1813. //-----------------------------------------------------------------------------
  1814. void CMatchmaking::RunFrame()
  1815. {
  1816. if ( !m_bInitialized )
  1817. {
  1818. RunFrameInvite();
  1819. return;
  1820. }
  1821. if ( NET_IsMultiplayer() )
  1822. {
  1823. NET_ProcessSocket( NS_MATCHMAKING, this );
  1824. if ( m_Session.IsSystemLink() )
  1825. {
  1826. NET_ProcessSocket( NS_SYSTEMLINK, this );
  1827. }
  1828. }
  1829. #if defined( _X360 )
  1830. if ( Audio_GetXVoice() != NULL )
  1831. {
  1832. if ( !GameIsActive() )
  1833. {
  1834. if ( Audio_GetXVoice()->VoiceUpdateData() == true )
  1835. {
  1836. CLC_VoiceData voice;
  1837. Audio_GetXVoice()->GetVoiceData( &voice );
  1838. if ( m_Session.IsHost() )
  1839. {
  1840. // Send this message on to everyone else
  1841. SendToRemoteClients( &voice, true );
  1842. }
  1843. else
  1844. {
  1845. // Send to the host
  1846. SendMessage( &voice, &m_Host );
  1847. }
  1848. Audio_GetXVoice()->VoiceResetLocalData();
  1849. }
  1850. UpdateVoiceStatus();
  1851. }
  1852. }
  1853. #endif
  1854. // Tell the session to run its update
  1855. m_Session.RunFrame();
  1856. // Check state:
  1857. switch( m_CurrentState )
  1858. {
  1859. case MMSTATE_CREATING:
  1860. // Waiting for success or failure from CreateSession()
  1861. // GameUI is displaying a "Creating Game" dialog.
  1862. break;
  1863. case MMSTATE_ACCEPTING_CONNECTIONS:
  1864. // Host is sitting in the Lobby waiting for connection requests. Once the game
  1865. // is full enough (player count >= min players) the host will be able to start the game.
  1866. UpdateAcceptingConnections();
  1867. break;
  1868. case MMSTATE_SEARCHING:
  1869. UpdateSearch();
  1870. break;
  1871. case MMSTATE_WAITING_QOS:
  1872. UpdateQosLookup();
  1873. break;
  1874. case MMSTATE_SESSION_CONNECTING:
  1875. UpdateConnecting();
  1876. break;
  1877. case MMSTATE_PREGAME:
  1878. UpdatePregame();
  1879. break;
  1880. case MMSTATE_MODIFYING:
  1881. UpdateSessionModify();
  1882. break;
  1883. case MMSTATE_HOSTMIGRATE_WAITINGFORCLIENTS:
  1884. if ( GetTime() - m_fSendTimer > HOSTMIGRATION_RETRYINTERVAL )
  1885. {
  1886. if ( m_nSendCount > HOSTMIGRATION_MAXRETRIES )
  1887. {
  1888. EndMigration();
  1889. }
  1890. else
  1891. {
  1892. TellClientsToMigrate();
  1893. }
  1894. }
  1895. break;
  1896. case MMSTATE_HOSTMIGRATE_WAITINGFORHOST:
  1897. if ( GetTime() - m_fWaitTimer > HOSTMIGRATION_MAXWAITTIME )
  1898. {
  1899. // Give up on that host and try the next one in the list
  1900. Msg( "Giving up on this host\n" );
  1901. ClientDropped( m_pNewHost );
  1902. StartHostMigration();
  1903. }
  1904. break;
  1905. case MMSTATE_SESSION_DISCONNECTING:
  1906. // Wait for the host reply, or timeout
  1907. if ( GetTime() - m_fWaitTimer > DISCONNECT_WAITTIME )
  1908. {
  1909. PerformDisconnect();
  1910. }
  1911. break;
  1912. case MMSTATE_REPORTING_STATS:
  1913. if ( GetTime() - m_fWaitTimer > REPORTSTATS_WAITTIME )
  1914. {
  1915. EndStatsReporting();
  1916. }
  1917. break;
  1918. }
  1919. CleanupMarkedChannels();
  1920. SendHeartbeat();
  1921. if ( m_bCleanup )
  1922. {
  1923. Cleanup();
  1924. }
  1925. RunFrameInvite();
  1926. }
  1927. //-----------------------------------------------------------------------------
  1928. // Purpose: Let the invite system determine a good moment to start connecting to inviter's host
  1929. //-----------------------------------------------------------------------------
  1930. void CMatchmaking::RunFrameInvite()
  1931. {
  1932. if ( m_InviteState != INVITE_NONE )
  1933. {
  1934. JoinInviteSession( &m_InviteSessionInfo );
  1935. }
  1936. }
  1937. //-----------------------------------------------------------------------------
  1938. // Purpose: Get Quality-of-Service with LIVE
  1939. //-----------------------------------------------------------------------------
  1940. MM_QOS_t CMatchmaking::GetQosWithLIVE()
  1941. {
  1942. return MM_GetQos();
  1943. }
  1944. //-----------------------------------------------------------------------------
  1945. // Debugging helpers
  1946. //-----------------------------------------------------------------------------
  1947. void CMatchmaking::ShowSessionInfo()
  1948. {
  1949. Msg( "[MM] Filled Slots:\n[MM] Public: %d of %d\n[MM] Private: %d of %d\n",
  1950. m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ),
  1951. m_Session.GetSessionSlots( SLOTS_TOTALPUBLIC ),
  1952. m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ),
  1953. m_Session.GetSessionSlots( SLOTS_TOTALPRIVATE ) );
  1954. Msg( "[MM] Current state: %d\n", m_CurrentState );
  1955. Msg( "[MM] Send timer: %f\n", GetTime() - m_fSendTimer );
  1956. Msg( "[MM] Wait timer: %f\n", GetTime() - m_fWaitTimer );
  1957. int total = 0;
  1958. for ( int i = 0; i < m_nTotalTeams; ++i )
  1959. {
  1960. total += CountPlayersOnTeam( i );
  1961. }
  1962. Msg( "[MM] Total players: %d\n", total );
  1963. EngineVGui()->SessionNotification( SESSION_NOTIFY_DUMPSTATS );
  1964. }
  1965. //-----------------------------------------------------------------------------
  1966. // Debugging helpers
  1967. //-----------------------------------------------------------------------------
  1968. void CMatchmaking::TestSendMessage()
  1969. {
  1970. for ( int i = 0; i < m_Remote.Count(); ++i )
  1971. {
  1972. AddRemoteChannel( &m_Remote[i]->m_adr );
  1973. }
  1974. NET_StringCmd msg;
  1975. SendToRemoteClients( &msg );
  1976. }
  1977. bool CMatchmaking::PreventFullServerStartup()
  1978. {
  1979. return m_bPreventFullServerStartup;
  1980. }
  1981. void CMatchmaking::SetPreventFullServerStartup( bool bState, char const *fmt, ... )
  1982. {
  1983. char desc[ 256 ];
  1984. va_list argptr;
  1985. va_start( argptr, fmt );
  1986. Q_vsnprintf( desc, sizeof( desc ), fmt, argptr );
  1987. va_end( argptr );
  1988. DevMsg( 1, "Setting state from prevent %s to prevent %s: %s",
  1989. m_bPreventFullServerStartup ? "yes" : "no",
  1990. bState ? "yes" : "no",
  1991. desc );
  1992. m_bPreventFullServerStartup = bState;
  1993. }
  1994. CON_COMMAND( mm_session_info, "Dump session information" )
  1995. {
  1996. if ( g_pMatchmaking )
  1997. {
  1998. g_pMatchmaking->ShowSessionInfo();
  1999. }
  2000. }
  2001. CON_COMMAND( mm_message, "Send a message to all remote clients" )
  2002. {
  2003. if ( g_pMatchmaking )
  2004. {
  2005. g_pMatchmaking->TestSendMessage();
  2006. }
  2007. }
  2008. CON_COMMAND( mm_stats, "" )
  2009. {
  2010. if ( g_pMatchmaking )
  2011. {
  2012. g_pMatchmaking->TestStats();
  2013. }
  2014. }