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.

797 lines
23 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 "vstdlib/random.h"
  9. #include "proto_oob.h"
  10. #include "cdll_engine_int.h"
  11. #include "matchmaking.h"
  12. #include "Session.h"
  13. #include "convar.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. static ConVar mm_minplayers( "mm_minplayers", "2", 0, "Number of players required to start an unranked game" );
  17. static ConVar mm_max_spectators( "mm_max_spectators", "4", 0, "Max players allowed on the spectator team" );
  18. //-----------------------------------------------------------------------------
  19. // Purpose: Start a Matchmaking session as the host
  20. //-----------------------------------------------------------------------------
  21. void CMatchmaking::StartHost( bool bSystemLink )
  22. {
  23. NET_SetMutiplayer( true );
  24. InitializeLocalClient( true );
  25. // Set all of the session contexts and properties. These were filled
  26. // in by GameUI after the user set them in the game options menu
  27. for ( int i = 0; i < m_SessionContexts.Count(); ++i )
  28. {
  29. XUSER_CONTEXT &ctx = m_SessionContexts[i];
  30. m_Session.SetContext( ctx.dwContextId, ctx.dwValue, false );
  31. }
  32. for ( int i = 0; i < m_SessionProperties.Count(); ++i )
  33. {
  34. XUSER_PROPERTY &prop = m_SessionProperties[i];
  35. m_Session.SetProperty( prop.dwPropertyId, prop.value.type, &prop.value.nData, false );
  36. }
  37. m_Session.SetSessionSlots( SLOTS_TOTALPUBLIC, m_nGameSize - m_nPrivateSlots );
  38. m_Session.SetSessionSlots( SLOTS_TOTALPRIVATE, m_nPrivateSlots );
  39. m_Session.SetIsSystemLink( bSystemLink );
  40. m_Session.SetIsHost( true );
  41. m_Session.SetOwnerId( XBX_GetPrimaryUserId() );
  42. // Session creation is asynchronous
  43. if ( !m_Session.CreateSession() )
  44. {
  45. SessionNotification( SESSION_NOTIFY_FAIL_CREATE );
  46. return;
  47. }
  48. // Waiting for session creation results
  49. SwitchToState( MMSTATE_CREATING );
  50. }
  51. //-----------------------------------------------------------------------------
  52. // Purpose: Successfully created the session
  53. //-----------------------------------------------------------------------------
  54. void CMatchmaking::OnHostSessionCreated()
  55. {
  56. Msg( "Host: CreateSession successful\n" );
  57. // Setup the game info that will be sent back to searching clients
  58. m_HostData.gameState = GAMESTATE_INLOBBY;
  59. KeyValues *pScenario = m_pSessionKeys->FindKey( "CONTEXT_SCENARIO" );
  60. if ( pScenario )
  61. {
  62. Q_strncpy( m_HostData.scenario, pScenario->GetString( "displaystring", "Unknown" ), sizeof( m_HostData.scenario ) );
  63. }
  64. KeyValues *pTime = m_pSessionKeys->FindKey( "PROPERTY_MAX_GAME_TIME" );
  65. if ( pTime )
  66. {
  67. m_HostData.gameTime = pTime->GetInt( "valuestring", 0 );
  68. }
  69. UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA );
  70. int iTeam = ChooseTeam();
  71. for ( int i = 0; i < m_Local.m_cPlayers; ++i )
  72. {
  73. m_Local.m_iTeam[i] = iTeam;
  74. }
  75. AddPlayersToSession( &m_Local );
  76. SendPlayerInfoToLobby( &m_Local, 0 );
  77. // Send session properties to client.dll so it can properly
  78. // set up game rules, cvars, etc.
  79. g_ClientDLL->SetupGameProperties( m_SessionContexts, m_SessionProperties );
  80. // Session has been created and advertised. Start listening for connection requests.
  81. SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS );
  82. }
  83. //-----------------------------------------------------------------------------
  84. // Purpose: Handle queries for system link servers
  85. //-----------------------------------------------------------------------------
  86. void CMatchmaking::HandleSystemLinkSearch( netpacket_t *pPacket )
  87. {
  88. // Should we respond to this probe?
  89. if ( !m_Session.IsSystemLink() ||
  90. !m_Session.IsHost() ||
  91. m_Session.IsFull() ||
  92. m_CurrentState < MMSTATE_ACCEPTING_CONNECTIONS )
  93. {
  94. return;
  95. }
  96. uint64 nonce = pPacket->message.ReadLongLong();
  97. // Send back info about our session
  98. XSESSION_INFO info;
  99. m_Session.GetSessionInfo( &info );
  100. char msg_buffer[MAX_ROUTABLE_PAYLOAD];
  101. bf_write msg( msg_buffer, sizeof(msg_buffer) );
  102. msg.WriteLong( CONNECTIONLESS_HEADER );
  103. msg.WriteByte( HTP_SYSTEMLINK_REPLY );
  104. msg.WriteLongLong( nonce );
  105. msg.WriteBytes( &info, sizeof( info ) );
  106. msg.WriteByte( m_Session.GetSessionSlots( SLOTS_TOTALPUBLIC ) - m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ) );
  107. msg.WriteByte( m_Session.GetSessionSlots( SLOTS_TOTALPRIVATE ) - m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ) );
  108. msg.WriteByte( m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ) );
  109. msg.WriteByte( m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ) );
  110. msg.WriteByte( m_nTotalTeams );
  111. msg.WriteByte( m_HostData.gameState );
  112. msg.WriteByte( m_HostData.gameTime );
  113. msg.WriteBytes( m_Local.m_szGamertags[0], MAX_PLAYER_NAME_LENGTH );
  114. msg.WriteBytes( m_HostData.scenario, MAX_MAP_NAME );
  115. msg.WriteByte( m_SessionProperties.Count() );
  116. msg.WriteByte( m_SessionContexts.Count() );
  117. uint nScenarioId = g_ClientDLL->GetPresenceID( "CONTEXT_SCENARIO" );
  118. uint nScenarioValue = 0;
  119. for ( int i = 0; i < m_SessionProperties.Count(); ++i )
  120. {
  121. XUSER_PROPERTY &prop = m_SessionProperties[i];
  122. msg.WriteBytes( &prop, sizeof( prop ) );
  123. }
  124. for ( int i = 0; i < m_SessionContexts.Count(); ++i )
  125. {
  126. XUSER_CONTEXT &ctx = m_SessionContexts[i];
  127. msg.WriteBytes( &ctx, sizeof( ctx ) );
  128. // Get the scenario id so the correct info can be displayed in the session browser
  129. if ( ctx.dwContextId == nScenarioId )
  130. {
  131. nScenarioValue = ctx.dwValue;
  132. }
  133. }
  134. msg.WriteByte( nScenarioValue );
  135. msg.WriteLongLong( m_HostData.xuid );
  136. netadr_t adr;
  137. adr.SetType( NA_BROADCAST );
  138. adr.SetPort( PORT_SYSTEMLINK );
  139. // Send message
  140. NET_SendPacket( NULL, NS_SYSTEMLINK, adr, msg.GetData(), msg.GetNumBytesWritten() );
  141. }
  142. //-----------------------------------------------------------------------------
  143. // Purpose: Process a client request to join the matchmaking session. If the
  144. // request is accepted, transmit session info to the new client, and
  145. // notify all connected clients of the new addition.
  146. //-----------------------------------------------------------------------------
  147. void CMatchmaking::HandleJoinRequest( netpacket_t *pPacket )
  148. {
  149. MM_JoinResponse joinResponse;
  150. CClientInfo tempClient;
  151. Msg( "Received a join request\n" );
  152. // Check the state
  153. if ( !IsAcceptingConnections() )
  154. {
  155. Msg( "State %d: Not accepting connections.\n", m_CurrentState );
  156. joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_NOTHOSTING;
  157. }
  158. else
  159. {
  160. // Extract some packet data to see if this client was invited
  161. tempClient.m_id = pPacket->message.ReadLongLong(); // 64 bit
  162. tempClient.m_cPlayers = pPacket->message.ReadByte();
  163. tempClient.m_bInvited = (pPacket->message.ReadOneBit() != 0);
  164. for ( int i = 0; i < m_Remote.Count(); i++ )
  165. {
  166. CClientInfo *pClient = m_Remote[i];
  167. if ( pClient )
  168. {
  169. if ( pClient->m_id == tempClient.m_id )
  170. {
  171. ClientDropped( pClient );
  172. break;
  173. }
  174. }
  175. }
  176. // Make sure there's room for new players
  177. int nSlotsOpen = m_Session.GetSessionSlots( SLOTS_TOTALPUBLIC ) - m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC );
  178. if ( tempClient.m_bInvited )
  179. {
  180. // Only invited clients can take private slots
  181. nSlotsOpen += m_Session.GetSessionSlots( SLOTS_TOTALPRIVATE ) - m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE );
  182. }
  183. if ( tempClient.m_cPlayers > nSlotsOpen )
  184. {
  185. Msg( "Session Full.\n" );
  186. joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_SESSIONFULL;
  187. }
  188. else
  189. {
  190. // There is room for the new client - fill out the rest of the response fields
  191. joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_APPROVED;
  192. joinResponse.m_id = m_Local.m_id;
  193. joinResponse.m_Nonce = m_Session.GetSessionNonce();
  194. joinResponse.m_SessionFlags = m_Session.GetSessionFlags();
  195. joinResponse.m_nOwnerId = XBX_GetPrimaryUserId();
  196. joinResponse.m_nTotalTeams = m_nTotalTeams;
  197. joinResponse.m_ContextCount = m_SessionContexts.Count();
  198. joinResponse.m_PropertyCount = m_SessionProperties.Count();
  199. for ( int i = 0; i < m_SessionContexts.Count(); ++i )
  200. {
  201. joinResponse.m_SessionContexts.AddToTail( m_SessionContexts[i] );
  202. }
  203. for ( int i = 0; i < m_SessionProperties.Count(); ++i )
  204. {
  205. joinResponse.m_SessionProperties.AddToTail( m_SessionProperties[i] );
  206. }
  207. if ( !GameIsActive() )
  208. {
  209. // If the game is in progress, the new client will choose a team after connecting
  210. joinResponse.m_iTeam = ChooseTeam();
  211. }
  212. else
  213. {
  214. // Tell the client to join the game in progress
  215. joinResponse.m_iTeam = -1;
  216. joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_APPROVED_JOINGAME;
  217. }
  218. }
  219. }
  220. netadr_t *pFromAdr = &pPacket->from;
  221. // Create a network channel for this client
  222. INetChannel *pNetChannel = AddRemoteChannel( pFromAdr );
  223. if ( !pNetChannel )
  224. {
  225. // Handle the error
  226. return;
  227. }
  228. // Send the response
  229. SendMessage( &joinResponse, pFromAdr );
  230. if ( joinResponse.m_ResponseType == joinResponse.JOINRESPONSE_APPROVED ||
  231. joinResponse.m_ResponseType == joinResponse.JOINRESPONSE_APPROVED_JOINGAME )
  232. {
  233. Msg( "Join request approved\n" );
  234. // Create a new client
  235. CClientInfo *pNewClient = new CClientInfo();
  236. pNewClient->m_adr = pPacket->from;
  237. pNewClient->m_id = tempClient.m_id; // 64 bit
  238. pNewClient->m_cPlayers = tempClient.m_cPlayers;
  239. pNewClient->m_bInvited = tempClient.m_bInvited;
  240. pPacket->message.ReadBytes( &pNewClient->m_xnaddr, sizeof( pNewClient->m_xnaddr ) );
  241. for ( int i = 0; i < tempClient.m_cPlayers; ++i )
  242. {
  243. pNewClient->m_xuids[i] = pPacket->message.ReadLongLong(); // 64 bit
  244. pPacket->message.ReadBytes( &pNewClient->m_cVoiceState, sizeof( pNewClient->m_cVoiceState ) );
  245. pNewClient->m_iTeam[i] = joinResponse.m_iTeam;
  246. pPacket->message.ReadString( pNewClient->m_szGamertags[i], sizeof( pNewClient->m_szGamertags[i] ), true );
  247. }
  248. // Tell everyone about the new client, and vice versa
  249. MM_ClientInfo newClientInfo;
  250. ClientInfoToNetMessage( &newClientInfo, pNewClient );
  251. for ( int i = 0; i < m_Remote.Count(); ++i )
  252. {
  253. CClientInfo *pRemote = m_Remote[i];
  254. MM_ClientInfo oldClientInfo;
  255. ClientInfoToNetMessage( &oldClientInfo, pRemote );
  256. SendMessage( &newClientInfo, pRemote );
  257. SendMessage( &oldClientInfo, pNewClient );
  258. }
  259. // Tell new client about the host (us)
  260. MM_ClientInfo hostInfo;
  261. ClientInfoToNetMessage( &hostInfo, &m_Local );
  262. SendMessage( &hostInfo, pNewClient );
  263. // Tell ourselves about the new client
  264. ProcessClientInfo( &newClientInfo );
  265. if ( GameIsActive() )
  266. {
  267. // Set a longer timeout for communication loss during loading
  268. SetChannelTimeout( &pNewClient->m_adr, HEARTBEAT_TIMEOUT_LOADING );
  269. // Tell the client to connect to the server
  270. MM_Checkpoint msg;
  271. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_CONNECT;
  272. SendMessage( &msg, pNewClient );
  273. }
  274. else
  275. {
  276. // UpdateServerNegotiation();
  277. }
  278. }
  279. else
  280. {
  281. // Join request was denied - close the channel
  282. RemoveRemoteChannel( pFromAdr, "Join request denied" );
  283. }
  284. }
  285. //-----------------------------------------------------------------------------
  286. // Purpose: Check the state of the lobby
  287. //-----------------------------------------------------------------------------
  288. void CMatchmaking::UpdateAcceptingConnections()
  289. {
  290. // Update host status
  291. UpdateSessionReplyData( XNET_QOS_LISTEN_SET_DATA );
  292. // Do nothing else
  293. }
  294. //-----------------------------------------------------------------------------
  295. // Purpose: Set the host data that gets sent in replies to client searches
  296. //-----------------------------------------------------------------------------
  297. void CMatchmaking::UpdateSessionReplyData( uint flags )
  298. {
  299. #if defined( _X360 )
  300. if ( m_Session.GetSessionHandle() == INVALID_HANDLE_VALUE )
  301. return;
  302. // Enable listening for client Quality Of Service probes
  303. XNKID id = m_Session.GetSessionId();
  304. uint ret = XNetQosListen( &id, (BYTE*)&m_HostData, sizeof( m_HostData ), 0, flags );
  305. if ( ret )
  306. {
  307. Warning( "Failed to update QOS Listener\n" );
  308. }
  309. #endif
  310. }
  311. //-----------------------------------------------------------------------------
  312. // Purpose: Change properties of the current session
  313. //-----------------------------------------------------------------------------
  314. void CMatchmaking::ModifySession()
  315. {
  316. // Notify all clients of the new settings and wait for replies
  317. for ( int i = 0; i < m_Remote.Count(); ++i )
  318. {
  319. m_Remote[i]->m_bModified = false;
  320. }
  321. SendModifySessionMessage();
  322. SessionNotification( SESSION_NOFIFY_MODIFYING_SESSION );
  323. }
  324. //-----------------------------------------------------------------------------
  325. // Purpose: Send the new session properties to all clients
  326. //-----------------------------------------------------------------------------
  327. void CMatchmaking::SendModifySessionMessage()
  328. {
  329. MM_JoinResponse msg;
  330. msg.m_ResponseType = MM_JoinResponse::JOINRESPONSE_MODIFY_SESSION;
  331. msg.m_ContextCount = m_SessionContexts.Count();
  332. msg.m_PropertyCount = m_SessionProperties.Count();
  333. for ( int i = 0; i < m_SessionProperties.Count(); ++i )
  334. {
  335. msg.m_SessionProperties.AddToTail( m_SessionProperties[i] );
  336. }
  337. for ( int i = 0; i < m_SessionContexts.Count(); ++i )
  338. {
  339. msg.m_SessionContexts.AddToTail( m_SessionContexts[i] );
  340. }
  341. SendToRemoteClients( &msg );
  342. // Wait for clients to respond before continuing
  343. m_fWaitTimer = GetTime();
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Purpose: Waiting for clients to modify their session properties
  347. //-----------------------------------------------------------------------------
  348. void CMatchmaking::UpdateSessionModify()
  349. {
  350. if ( !m_Session.IsHost() )
  351. return;
  352. bool bFinished = true;
  353. if ( GetTime() - m_fWaitTimer < SESSIONMODIRY_MAXWAITTIME )
  354. {
  355. // Check if all clients have finished modifying the session
  356. for ( int i = 0; i < m_Remote.Count(); ++i )
  357. {
  358. if ( !m_Remote[i]->m_bModified )
  359. {
  360. bFinished = false;
  361. }
  362. }
  363. }
  364. if ( bFinished )
  365. {
  366. EndSessionModify();
  367. }
  368. }
  369. //-----------------------------------------------------------------------------
  370. // Purpose: Finish session modification
  371. //-----------------------------------------------------------------------------
  372. void CMatchmaking::EndSessionModify()
  373. {
  374. // Drop any clients that didn't respond
  375. for ( int i = 0; i < m_Remote.Count(); ++i )
  376. {
  377. if ( !m_Remote[i]->m_bModified )
  378. {
  379. KickPlayerFromSession( m_Remote[i]->m_id );
  380. }
  381. }
  382. // Send session properties to client.dll so it can properly
  383. // set up game rules, cvars, etc.
  384. g_ClientDLL->SetupGameProperties( m_SessionContexts, m_SessionProperties );
  385. SessionNotification( SESSION_NOTIFY_MODIFYING_COMPLETED_HOST );
  386. }
  387. //-----------------------------------------------------------------------------
  388. // Purpose: Handle a client registration response
  389. //-----------------------------------------------------------------------------
  390. bool CMatchmaking::ProcessRegisterResponse( MM_RegisterResponse *msg )
  391. {
  392. // Check if all clients have registered
  393. bool bAllRegistered = true;
  394. INetChannel *pChannel = msg->GetNetChannel();
  395. for ( int i = 0; i < m_Remote.Count(); ++i )
  396. {
  397. CClientInfo *pClient = m_Remote[i];
  398. if ( pClient->m_adr.CompareAdr( pChannel->GetRemoteAddress() ) )
  399. {
  400. pClient->m_bRegistered = true;
  401. }
  402. if ( !pClient->m_bRegistered )
  403. {
  404. bAllRegistered = false;
  405. }
  406. }
  407. if ( bAllRegistered )
  408. {
  409. // Everyone's registered, register ourselves
  410. m_Local.m_bRegistered = true;
  411. m_Session.RegisterForArbitration();
  412. }
  413. return true;
  414. }
  415. //-----------------------------------------------------------------------------
  416. // Purpose: Find out if all clients registered for arbitration
  417. //-----------------------------------------------------------------------------
  418. void CMatchmaking::ProcessRegistrationResults()
  419. {
  420. // Clear all the client registration flags
  421. for ( int i = 0; i < m_Remote.Count(); ++i )
  422. {
  423. m_Remote[i]->m_bRegistered = false;
  424. }
  425. XSESSION_REGISTRATION_RESULTS *pResults = m_Session.GetRegistrationResults();
  426. Assert( pResults );
  427. int numRegistrants = pResults->wNumRegistrants;
  428. Msg( "%d players registered for arbitration\n", numRegistrants );
  429. for ( int i = 0; i < numRegistrants; ++i )
  430. {
  431. for ( int j = 0; j < m_Remote.Count(); ++j )
  432. {
  433. if ( m_Remote[j]->m_id == pResults->rgRegistrants[i].qwMachineID )
  434. {
  435. m_Remote[j]->m_bRegistered = true;
  436. }
  437. }
  438. }
  439. // Now drop any clients that didn't register
  440. for ( int i = m_Remote.Count()-1; i >= 0; --i )
  441. {
  442. if ( !m_Remote[i]->m_bRegistered )
  443. {
  444. ClientDropped( m_Remote[i] );
  445. }
  446. }
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose: All clients are in the lobby and ready - perform pre-game setup
  450. //-----------------------------------------------------------------------------
  451. bool CMatchmaking::StartGame()
  452. {
  453. if ( !m_Session.IsHost() )
  454. return false;
  455. if ( GetPlayersNeeded() != 0 )
  456. return false;
  457. StartCountdown();
  458. return true;
  459. }
  460. //-----------------------------------------------------------------------------
  461. // Purpose: All clients are in the lobby and ready - perform pre-game setup
  462. //-----------------------------------------------------------------------------
  463. bool CMatchmaking::CancelStartGame()
  464. {
  465. if ( !m_Session.IsHost() )
  466. return false;
  467. CancelCountdown();
  468. return true;
  469. }
  470. //-----------------------------------------------------------------------------
  471. // Purpose: Start the countdown timer for game start
  472. //-----------------------------------------------------------------------------
  473. void CMatchmaking::StartCountdown()
  474. {
  475. if ( m_Session.IsArbitrated() )
  476. {
  477. // Initialize the client flags
  478. for ( int i = 0; i < m_Remote.Count(); ++i )
  479. {
  480. m_Remote[i]->m_bRegistered = false;
  481. }
  482. m_Local.m_bRegistered = false;
  483. }
  484. // Block searches while we're loading the game, because we can't reply anyway
  485. UpdateSessionReplyData( XNET_QOS_LISTEN_DISABLE );
  486. // Send the start game message to everyone
  487. MM_Checkpoint msg;
  488. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_PREGAME;
  489. SendToRemoteClients( &msg );
  490. ProcessCheckpoint( &msg );
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Purpose:
  494. //-----------------------------------------------------------------------------
  495. void CMatchmaking::CancelCountdown()
  496. {
  497. // Accept searches again
  498. UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA );
  499. MM_Checkpoint msg;
  500. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_GAME_LOBBY;
  501. SendToRemoteClients( &msg );
  502. ProcessCheckpoint( &msg );
  503. }
  504. //-----------------------------------------------------------------------------
  505. // Purpose: Tell the clients to connect to the server
  506. //-----------------------------------------------------------------------------
  507. void CMatchmaking::TellClientsToConnect()
  508. {
  509. if ( IsServer() )
  510. {
  511. MM_Checkpoint msg;
  512. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_CONNECT;
  513. if ( m_Session.IsHost() )
  514. {
  515. ProcessCheckpoint( &msg );
  516. }
  517. else
  518. {
  519. SendMessage( &msg, &m_Host );
  520. }
  521. SwitchToState( MMSTATE_CONNECTED_TO_SERVER );
  522. }
  523. }
  524. //-----------------------------------------------------------------------------
  525. // Purpose: Tell the clients the game is over
  526. //-----------------------------------------------------------------------------
  527. void CMatchmaking::EndGame()
  528. {
  529. if ( m_Session.IsHost() && GameIsActive() )
  530. {
  531. MM_Checkpoint msg;
  532. if ( !m_Session.IsSystemLink() )
  533. {
  534. // Tell clients to report stats to live.
  535. for ( int i = 0; i < m_Remote.Count(); ++i )
  536. {
  537. m_Remote[i]->m_bReportedStats = false;
  538. }
  539. m_Local.m_bReportedStats = false;
  540. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_REPORT_STATS;
  541. }
  542. else
  543. {
  544. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_POSTGAME;
  545. }
  546. SendToRemoteClients( &msg );
  547. ProcessCheckpoint( &msg );
  548. }
  549. }
  550. //-----------------------------------------------------------------------------
  551. // Purpose: End the stats reporting phase
  552. //-----------------------------------------------------------------------------
  553. void CMatchmaking::EndStatsReporting()
  554. {
  555. if ( m_CurrentState == MMSTATE_REPORTING_STATS )
  556. {
  557. if ( !m_Session.IsHost() )
  558. {
  559. // Notify the host that we've finished reporting stats
  560. MM_Checkpoint msg;
  561. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_REPORTING_COMPLETE;
  562. SendMessage( &msg, &m_Host );
  563. }
  564. else
  565. {
  566. // Remove anyone who didn't report stats
  567. // Drop any clients that didn't respond
  568. for ( int i = 0; i < m_Remote.Count(); ++i )
  569. {
  570. if ( !m_Remote[i]->m_bReportedStats )
  571. {
  572. KickPlayerFromSession( m_Remote[i]->m_id );
  573. }
  574. }
  575. // Everyone has reported stats
  576. MM_Checkpoint msg;
  577. msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_POSTGAME;
  578. SendToRemoteClients( &msg );
  579. ProcessCheckpoint( &msg );
  580. }
  581. }
  582. }
  583. //-----------------------------------------------------------------------------
  584. // Purpose: Count players on a given team
  585. //-----------------------------------------------------------------------------
  586. int CMatchmaking::CountPlayersOnTeam( int idxTeam )
  587. {
  588. int numPlayers = 0;
  589. for ( int i = 0; i < m_Remote.Count(); ++i )
  590. {
  591. if ( !m_Remote[i] )
  592. continue;
  593. CClientInfo &ciRemote = *m_Remote[i];
  594. for ( int jp = 0; jp < ciRemote.m_cPlayers; ++ jp )
  595. {
  596. if ( ciRemote.m_iTeam[jp] == idxTeam )
  597. {
  598. ++ numPlayers;
  599. }
  600. }
  601. }
  602. for ( int jp = 0; jp < m_Local.m_cPlayers; ++ jp )
  603. {
  604. if ( m_Local.m_iTeam[jp] == idxTeam )
  605. {
  606. ++ numPlayers;
  607. }
  608. }
  609. if ( !m_Session.IsHost() )
  610. {
  611. for ( int jp = 0; jp < m_Host.m_cPlayers; ++ jp )
  612. {
  613. if ( m_Host.m_iTeam[jp] == idxTeam )
  614. {
  615. ++ numPlayers;
  616. }
  617. }
  618. }
  619. return numPlayers;
  620. }
  621. //-----------------------------------------------------------------------------
  622. // Purpose: Auto-assign players as they first enter the lobby
  623. //-----------------------------------------------------------------------------
  624. int CMatchmaking::ChooseTeam()
  625. {
  626. int iTeam = -1;
  627. for ( int i = 0; i < m_nTotalTeams - 1; ++i )
  628. {
  629. int numI = CountPlayersOnTeam( i ), numIp1 = CountPlayersOnTeam( i + 1 );
  630. if ( numI < numIp1 )
  631. {
  632. iTeam = i;
  633. }
  634. else if ( numI > numIp1 )
  635. {
  636. iTeam = i + 1;
  637. }
  638. }
  639. if ( iTeam == -1 )
  640. {
  641. iTeam = RandomInt( 0, m_nTotalTeams - 1 );
  642. }
  643. return iTeam;
  644. }
  645. //-----------------------------------------------------------------------------
  646. // Purpose: Switch this client to the next team with open slots
  647. //-----------------------------------------------------------------------------
  648. void CMatchmaking::SwitchToNextOpenTeam( CClientInfo *pClient )
  649. {
  650. int oldTeam = pClient->m_iTeam[0];
  651. int maxPlayersPerTeam = m_nGameSize / m_nTotalTeams + 3;
  652. // Choose the next team for this client
  653. int iTeam = oldTeam;
  654. do
  655. {
  656. iTeam = (iTeam + 1) % m_nTotalTeams;
  657. if ( CountPlayersOnTeam( iTeam ) < maxPlayersPerTeam )
  658. break;
  659. } while( iTeam != oldTeam );
  660. MM_ClientInfo info;
  661. ClientInfoToNetMessage( &info, pClient );
  662. for ( int i = 0; i < pClient->m_cPlayers; ++i )
  663. {
  664. info.m_iTeam[i] = iTeam;
  665. }
  666. // Send to ourselves and everyone else
  667. ProcessClientInfo( &info );
  668. }
  669. //-----------------------------------------------------------------------------
  670. // Purpose: Check if the teams are full enough to start
  671. //-----------------------------------------------------------------------------
  672. int CMatchmaking::GetPlayersNeeded()
  673. {
  674. // System link can always be started
  675. if ( m_Session.IsSystemLink() )
  676. return 0;
  677. // check if we can start the game
  678. int total = 0;
  679. for ( int i = 0; i < m_nTotalTeams; ++i )
  680. {
  681. total += CountPlayersOnTeam( i );
  682. }
  683. return max( 0, mm_minplayers.GetInt() - max( 0, total ) );
  684. }