diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/matchmakinghost.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/matchmakinghost.cpp')
| -rw-r--r-- | engine/matchmakinghost.cpp | 797 |
1 files changed, 797 insertions, 0 deletions
diff --git a/engine/matchmakinghost.cpp b/engine/matchmakinghost.cpp new file mode 100644 index 0000000..1649b3a --- /dev/null +++ b/engine/matchmakinghost.cpp @@ -0,0 +1,797 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Handles joining clients together in a matchmaking session before a multiplayer +// game, tracking new players and dropped players during the game, and reporting +// game results and stats after the game is complete. +// +//=============================================================================// + +#include "vstdlib/random.h" +#include "proto_oob.h" +#include "cdll_engine_int.h" +#include "matchmaking.h" +#include "Session.h" +#include "convar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static ConVar mm_minplayers( "mm_minplayers", "2", 0, "Number of players required to start an unranked game" ); +static ConVar mm_max_spectators( "mm_max_spectators", "4", 0, "Max players allowed on the spectator team" ); + +//----------------------------------------------------------------------------- +// Purpose: Start a Matchmaking session as the host +//----------------------------------------------------------------------------- +void CMatchmaking::StartHost( bool bSystemLink ) +{ + NET_SetMutiplayer( true ); + + InitializeLocalClient( true ); + + // Set all of the session contexts and properties. These were filled + // in by GameUI after the user set them in the game options menu + for ( int i = 0; i < m_SessionContexts.Count(); ++i ) + { + XUSER_CONTEXT &ctx = m_SessionContexts[i]; + m_Session.SetContext( ctx.dwContextId, ctx.dwValue, false ); + } + + for ( int i = 0; i < m_SessionProperties.Count(); ++i ) + { + XUSER_PROPERTY &prop = m_SessionProperties[i]; + m_Session.SetProperty( prop.dwPropertyId, prop.value.type, &prop.value.nData, false ); + } + + m_Session.SetSessionSlots( SLOTS_TOTALPUBLIC, m_nGameSize - m_nPrivateSlots ); + m_Session.SetSessionSlots( SLOTS_TOTALPRIVATE, m_nPrivateSlots ); + + m_Session.SetIsSystemLink( bSystemLink ); + m_Session.SetIsHost( true ); + m_Session.SetOwnerId( XBX_GetPrimaryUserId() ); + + // Session creation is asynchronous + if ( !m_Session.CreateSession() ) + { + SessionNotification( SESSION_NOTIFY_FAIL_CREATE ); + return; + } + + // Waiting for session creation results + SwitchToState( MMSTATE_CREATING ); +} + +//----------------------------------------------------------------------------- +// Purpose: Successfully created the session +//----------------------------------------------------------------------------- +void CMatchmaking::OnHostSessionCreated() +{ + Msg( "Host: CreateSession successful\n" ); + + // Setup the game info that will be sent back to searching clients + m_HostData.gameState = GAMESTATE_INLOBBY; + KeyValues *pScenario = m_pSessionKeys->FindKey( "CONTEXT_SCENARIO" ); + if ( pScenario ) + { + Q_strncpy( m_HostData.scenario, pScenario->GetString( "displaystring", "Unknown" ), sizeof( m_HostData.scenario ) ); + } + KeyValues *pTime = m_pSessionKeys->FindKey( "PROPERTY_MAX_GAME_TIME" ); + if ( pTime ) + { + m_HostData.gameTime = pTime->GetInt( "valuestring", 0 ); + } + UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA ); + + int iTeam = ChooseTeam(); + for ( int i = 0; i < m_Local.m_cPlayers; ++i ) + { + m_Local.m_iTeam[i] = iTeam; + } + + AddPlayersToSession( &m_Local ); + SendPlayerInfoToLobby( &m_Local, 0 ); + + // Send session properties to client.dll so it can properly + // set up game rules, cvars, etc. + g_ClientDLL->SetupGameProperties( m_SessionContexts, m_SessionProperties ); + + // Session has been created and advertised. Start listening for connection requests. + SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle queries for system link servers +//----------------------------------------------------------------------------- +void CMatchmaking::HandleSystemLinkSearch( netpacket_t *pPacket ) +{ + // Should we respond to this probe? + if ( !m_Session.IsSystemLink() || + !m_Session.IsHost() || + m_Session.IsFull() || + m_CurrentState < MMSTATE_ACCEPTING_CONNECTIONS ) + { + return; + } + + uint64 nonce = pPacket->message.ReadLongLong(); + + // Send back info about our session + XSESSION_INFO info; + m_Session.GetSessionInfo( &info ); + + char msg_buffer[MAX_ROUTABLE_PAYLOAD]; + bf_write msg( msg_buffer, sizeof(msg_buffer) ); + + msg.WriteLong( CONNECTIONLESS_HEADER ); + msg.WriteByte( HTP_SYSTEMLINK_REPLY ); + msg.WriteLongLong( nonce ); + msg.WriteBytes( &info, sizeof( info ) ); + msg.WriteByte( m_Session.GetSessionSlots( SLOTS_TOTALPUBLIC ) - m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ) ); + msg.WriteByte( m_Session.GetSessionSlots( SLOTS_TOTALPRIVATE ) - m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ) ); + msg.WriteByte( m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ) ); + msg.WriteByte( m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ) ); + msg.WriteByte( m_nTotalTeams ); + msg.WriteByte( m_HostData.gameState ); + msg.WriteByte( m_HostData.gameTime ); + msg.WriteBytes( m_Local.m_szGamertags[0], MAX_PLAYER_NAME_LENGTH ); + msg.WriteBytes( m_HostData.scenario, MAX_MAP_NAME ); + msg.WriteByte( m_SessionProperties.Count() ); + msg.WriteByte( m_SessionContexts.Count() ); + + uint nScenarioId = g_ClientDLL->GetPresenceID( "CONTEXT_SCENARIO" ); + uint nScenarioValue = 0; + + for ( int i = 0; i < m_SessionProperties.Count(); ++i ) + { + XUSER_PROPERTY &prop = m_SessionProperties[i]; + msg.WriteBytes( &prop, sizeof( prop ) ); + } + for ( int i = 0; i < m_SessionContexts.Count(); ++i ) + { + XUSER_CONTEXT &ctx = m_SessionContexts[i]; + msg.WriteBytes( &ctx, sizeof( ctx ) ); + + // Get the scenario id so the correct info can be displayed in the session browser + if ( ctx.dwContextId == nScenarioId ) + { + nScenarioValue = ctx.dwValue; + } + } + msg.WriteByte( nScenarioValue ); + msg.WriteLongLong( m_HostData.xuid ); + + netadr_t adr; + adr.SetType( NA_BROADCAST ); + adr.SetPort( PORT_SYSTEMLINK ); + + // Send message + NET_SendPacket( NULL, NS_SYSTEMLINK, adr, msg.GetData(), msg.GetNumBytesWritten() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Process a client request to join the matchmaking session. If the +// request is accepted, transmit session info to the new client, and +// notify all connected clients of the new addition. +//----------------------------------------------------------------------------- +void CMatchmaking::HandleJoinRequest( netpacket_t *pPacket ) +{ + MM_JoinResponse joinResponse; + CClientInfo tempClient; + + Msg( "Received a join request\n" ); + + // Check the state + if ( !IsAcceptingConnections() ) + { + Msg( "State %d: Not accepting connections.\n", m_CurrentState ); + joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_NOTHOSTING; + } + else + { + // Extract some packet data to see if this client was invited + tempClient.m_id = pPacket->message.ReadLongLong(); // 64 bit + tempClient.m_cPlayers = pPacket->message.ReadByte(); + tempClient.m_bInvited = (pPacket->message.ReadOneBit() != 0); + + for ( int i = 0; i < m_Remote.Count(); i++ ) + { + CClientInfo *pClient = m_Remote[i]; + + if ( pClient ) + { + if ( pClient->m_id == tempClient.m_id ) + { + ClientDropped( pClient ); + break; + } + } + } + + // Make sure there's room for new players + int nSlotsOpen = m_Session.GetSessionSlots( SLOTS_TOTALPUBLIC ) - m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ); + if ( tempClient.m_bInvited ) + { + // Only invited clients can take private slots + nSlotsOpen += m_Session.GetSessionSlots( SLOTS_TOTALPRIVATE ) - m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ); + } + + if ( tempClient.m_cPlayers > nSlotsOpen ) + { + Msg( "Session Full.\n" ); + joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_SESSIONFULL; + } + else + { + // There is room for the new client - fill out the rest of the response fields + joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_APPROVED; + joinResponse.m_id = m_Local.m_id; + joinResponse.m_Nonce = m_Session.GetSessionNonce(); + joinResponse.m_SessionFlags = m_Session.GetSessionFlags(); + joinResponse.m_nOwnerId = XBX_GetPrimaryUserId(); + joinResponse.m_nTotalTeams = m_nTotalTeams; + joinResponse.m_ContextCount = m_SessionContexts.Count(); + joinResponse.m_PropertyCount = m_SessionProperties.Count(); + + for ( int i = 0; i < m_SessionContexts.Count(); ++i ) + { + joinResponse.m_SessionContexts.AddToTail( m_SessionContexts[i] ); + } + + for ( int i = 0; i < m_SessionProperties.Count(); ++i ) + { + joinResponse.m_SessionProperties.AddToTail( m_SessionProperties[i] ); + } + + if ( !GameIsActive() ) + { + // If the game is in progress, the new client will choose a team after connecting + joinResponse.m_iTeam = ChooseTeam(); + } + else + { + // Tell the client to join the game in progress + joinResponse.m_iTeam = -1; + joinResponse.m_ResponseType = joinResponse.JOINRESPONSE_APPROVED_JOINGAME; + } + } + } + + netadr_t *pFromAdr = &pPacket->from; + + // Create a network channel for this client + INetChannel *pNetChannel = AddRemoteChannel( pFromAdr ); + if ( !pNetChannel ) + { + // Handle the error + return; + } + + // Send the response + SendMessage( &joinResponse, pFromAdr ); + + if ( joinResponse.m_ResponseType == joinResponse.JOINRESPONSE_APPROVED || + joinResponse.m_ResponseType == joinResponse.JOINRESPONSE_APPROVED_JOINGAME ) + { + Msg( "Join request approved\n" ); + + // Create a new client + CClientInfo *pNewClient = new CClientInfo(); + + pNewClient->m_adr = pPacket->from; + pNewClient->m_id = tempClient.m_id; // 64 bit + pNewClient->m_cPlayers = tempClient.m_cPlayers; + pNewClient->m_bInvited = tempClient.m_bInvited; + pPacket->message.ReadBytes( &pNewClient->m_xnaddr, sizeof( pNewClient->m_xnaddr ) ); + for ( int i = 0; i < tempClient.m_cPlayers; ++i ) + { + pNewClient->m_xuids[i] = pPacket->message.ReadLongLong(); // 64 bit + pPacket->message.ReadBytes( &pNewClient->m_cVoiceState, sizeof( pNewClient->m_cVoiceState ) ); + pNewClient->m_iTeam[i] = joinResponse.m_iTeam; + pPacket->message.ReadString( pNewClient->m_szGamertags[i], sizeof( pNewClient->m_szGamertags[i] ), true ); + } + + // Tell everyone about the new client, and vice versa + MM_ClientInfo newClientInfo; + ClientInfoToNetMessage( &newClientInfo, pNewClient ); + + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + CClientInfo *pRemote = m_Remote[i]; + + MM_ClientInfo oldClientInfo; + ClientInfoToNetMessage( &oldClientInfo, pRemote ); + + SendMessage( &newClientInfo, pRemote ); + SendMessage( &oldClientInfo, pNewClient ); + } + + // Tell new client about the host (us) + MM_ClientInfo hostInfo; + ClientInfoToNetMessage( &hostInfo, &m_Local ); + SendMessage( &hostInfo, pNewClient ); + + // Tell ourselves about the new client + ProcessClientInfo( &newClientInfo ); + + if ( GameIsActive() ) + { + // Set a longer timeout for communication loss during loading + SetChannelTimeout( &pNewClient->m_adr, HEARTBEAT_TIMEOUT_LOADING ); + + // Tell the client to connect to the server + MM_Checkpoint msg; + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_CONNECT; + SendMessage( &msg, pNewClient ); + } + else + { + // UpdateServerNegotiation(); + } + } + else + { + // Join request was denied - close the channel + RemoveRemoteChannel( pFromAdr, "Join request denied" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Check the state of the lobby +//----------------------------------------------------------------------------- +void CMatchmaking::UpdateAcceptingConnections() +{ + // Update host status + UpdateSessionReplyData( XNET_QOS_LISTEN_SET_DATA ); + + // Do nothing else +} + +//----------------------------------------------------------------------------- +// Purpose: Set the host data that gets sent in replies to client searches +//----------------------------------------------------------------------------- +void CMatchmaking::UpdateSessionReplyData( uint flags ) +{ +#if defined( _X360 ) + if ( m_Session.GetSessionHandle() == INVALID_HANDLE_VALUE ) + return; + + // Enable listening for client Quality Of Service probes + XNKID id = m_Session.GetSessionId(); + uint ret = XNetQosListen( &id, (BYTE*)&m_HostData, sizeof( m_HostData ), 0, flags ); + if ( ret ) + { + Warning( "Failed to update QOS Listener\n" ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Change properties of the current session +//----------------------------------------------------------------------------- +void CMatchmaking::ModifySession() +{ + // Notify all clients of the new settings and wait for replies + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + m_Remote[i]->m_bModified = false; + } + + SendModifySessionMessage(); + + SessionNotification( SESSION_NOFIFY_MODIFYING_SESSION ); +} + +//----------------------------------------------------------------------------- +// Purpose: Send the new session properties to all clients +//----------------------------------------------------------------------------- +void CMatchmaking::SendModifySessionMessage() +{ + MM_JoinResponse msg; + msg.m_ResponseType = MM_JoinResponse::JOINRESPONSE_MODIFY_SESSION; + msg.m_ContextCount = m_SessionContexts.Count(); + msg.m_PropertyCount = m_SessionProperties.Count(); + + for ( int i = 0; i < m_SessionProperties.Count(); ++i ) + { + msg.m_SessionProperties.AddToTail( m_SessionProperties[i] ); + } + for ( int i = 0; i < m_SessionContexts.Count(); ++i ) + { + msg.m_SessionContexts.AddToTail( m_SessionContexts[i] ); + } + + SendToRemoteClients( &msg ); + + // Wait for clients to respond before continuing + m_fWaitTimer = GetTime(); +} + +//----------------------------------------------------------------------------- +// Purpose: Waiting for clients to modify their session properties +//----------------------------------------------------------------------------- +void CMatchmaking::UpdateSessionModify() +{ + if ( !m_Session.IsHost() ) + return; + + bool bFinished = true; + + if ( GetTime() - m_fWaitTimer < SESSIONMODIRY_MAXWAITTIME ) + { + // Check if all clients have finished modifying the session + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + if ( !m_Remote[i]->m_bModified ) + { + bFinished = false; + } + } + } + + if ( bFinished ) + { + EndSessionModify(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Finish session modification +//----------------------------------------------------------------------------- +void CMatchmaking::EndSessionModify() +{ + // Drop any clients that didn't respond + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + if ( !m_Remote[i]->m_bModified ) + { + KickPlayerFromSession( m_Remote[i]->m_id ); + } + } + + // Send session properties to client.dll so it can properly + // set up game rules, cvars, etc. + g_ClientDLL->SetupGameProperties( m_SessionContexts, m_SessionProperties ); + + SessionNotification( SESSION_NOTIFY_MODIFYING_COMPLETED_HOST ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle a client registration response +//----------------------------------------------------------------------------- +bool CMatchmaking::ProcessRegisterResponse( MM_RegisterResponse *msg ) +{ + // Check if all clients have registered + bool bAllRegistered = true; + INetChannel *pChannel = msg->GetNetChannel(); + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + CClientInfo *pClient = m_Remote[i]; + if ( pClient->m_adr.CompareAdr( pChannel->GetRemoteAddress() ) ) + { + pClient->m_bRegistered = true; + } + + if ( !pClient->m_bRegistered ) + { + bAllRegistered = false; + } + } + + if ( bAllRegistered ) + { + // Everyone's registered, register ourselves + m_Local.m_bRegistered = true; + m_Session.RegisterForArbitration(); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Find out if all clients registered for arbitration +//----------------------------------------------------------------------------- +void CMatchmaking::ProcessRegistrationResults() +{ + // Clear all the client registration flags + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + m_Remote[i]->m_bRegistered = false; + } + + XSESSION_REGISTRATION_RESULTS *pResults = m_Session.GetRegistrationResults(); + Assert( pResults ); + + int numRegistrants = pResults->wNumRegistrants; + Msg( "%d players registered for arbitration\n", numRegistrants ); + + for ( int i = 0; i < numRegistrants; ++i ) + { + for ( int j = 0; j < m_Remote.Count(); ++j ) + { + if ( m_Remote[j]->m_id == pResults->rgRegistrants[i].qwMachineID ) + { + m_Remote[j]->m_bRegistered = true; + } + } + } + + // Now drop any clients that didn't register + for ( int i = m_Remote.Count()-1; i >= 0; --i ) + { + if ( !m_Remote[i]->m_bRegistered ) + { + ClientDropped( m_Remote[i] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: All clients are in the lobby and ready - perform pre-game setup +//----------------------------------------------------------------------------- +bool CMatchmaking::StartGame() +{ + if ( !m_Session.IsHost() ) + return false; + + if ( GetPlayersNeeded() != 0 ) + return false; + + StartCountdown(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: All clients are in the lobby and ready - perform pre-game setup +//----------------------------------------------------------------------------- +bool CMatchmaking::CancelStartGame() +{ + if ( !m_Session.IsHost() ) + return false; + + CancelCountdown(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Start the countdown timer for game start +//----------------------------------------------------------------------------- +void CMatchmaking::StartCountdown() +{ + if ( m_Session.IsArbitrated() ) + { + // Initialize the client flags + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + m_Remote[i]->m_bRegistered = false; + } + m_Local.m_bRegistered = false; + } + + // Block searches while we're loading the game, because we can't reply anyway + UpdateSessionReplyData( XNET_QOS_LISTEN_DISABLE ); + + // Send the start game message to everyone + MM_Checkpoint msg; + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_PREGAME; + SendToRemoteClients( &msg ); + ProcessCheckpoint( &msg ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMatchmaking::CancelCountdown() +{ + // Accept searches again + UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA ); + + MM_Checkpoint msg; + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_GAME_LOBBY; + SendToRemoteClients( &msg ); + ProcessCheckpoint( &msg ); +} + +//----------------------------------------------------------------------------- +// Purpose: Tell the clients to connect to the server +//----------------------------------------------------------------------------- +void CMatchmaking::TellClientsToConnect() +{ + if ( IsServer() ) + { + MM_Checkpoint msg; + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_CONNECT; + if ( m_Session.IsHost() ) + { + ProcessCheckpoint( &msg ); + } + else + { + SendMessage( &msg, &m_Host ); + } + + SwitchToState( MMSTATE_CONNECTED_TO_SERVER ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tell the clients the game is over +//----------------------------------------------------------------------------- +void CMatchmaking::EndGame() +{ + if ( m_Session.IsHost() && GameIsActive() ) + { + MM_Checkpoint msg; + if ( !m_Session.IsSystemLink() ) + { + // Tell clients to report stats to live. + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + m_Remote[i]->m_bReportedStats = false; + } + m_Local.m_bReportedStats = false; + + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_REPORT_STATS; + } + else + { + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_POSTGAME; + } + + SendToRemoteClients( &msg ); + ProcessCheckpoint( &msg ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: End the stats reporting phase +//----------------------------------------------------------------------------- +void CMatchmaking::EndStatsReporting() +{ + if ( m_CurrentState == MMSTATE_REPORTING_STATS ) + { + if ( !m_Session.IsHost() ) + { + // Notify the host that we've finished reporting stats + MM_Checkpoint msg; + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_REPORTING_COMPLETE; + SendMessage( &msg, &m_Host ); + } + else + { + // Remove anyone who didn't report stats + // Drop any clients that didn't respond + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + if ( !m_Remote[i]->m_bReportedStats ) + { + KickPlayerFromSession( m_Remote[i]->m_id ); + } + } + + // Everyone has reported stats + MM_Checkpoint msg; + msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_POSTGAME; + SendToRemoteClients( &msg ); + ProcessCheckpoint( &msg ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Count players on a given team +//----------------------------------------------------------------------------- +int CMatchmaking::CountPlayersOnTeam( int idxTeam ) +{ + int numPlayers = 0; + for ( int i = 0; i < m_Remote.Count(); ++i ) + { + if ( !m_Remote[i] ) + continue; + + CClientInfo &ciRemote = *m_Remote[i]; + + for ( int jp = 0; jp < ciRemote.m_cPlayers; ++ jp ) + { + if ( ciRemote.m_iTeam[jp] == idxTeam ) + { + ++ numPlayers; + } + } + } + for ( int jp = 0; jp < m_Local.m_cPlayers; ++ jp ) + { + if ( m_Local.m_iTeam[jp] == idxTeam ) + { + ++ numPlayers; + } + } + if ( !m_Session.IsHost() ) + { + for ( int jp = 0; jp < m_Host.m_cPlayers; ++ jp ) + { + if ( m_Host.m_iTeam[jp] == idxTeam ) + { + ++ numPlayers; + } + } + } + return numPlayers; +} + +//----------------------------------------------------------------------------- +// Purpose: Auto-assign players as they first enter the lobby +//----------------------------------------------------------------------------- +int CMatchmaking::ChooseTeam() +{ + int iTeam = -1; + for ( int i = 0; i < m_nTotalTeams - 1; ++i ) + { + int numI = CountPlayersOnTeam( i ), numIp1 = CountPlayersOnTeam( i + 1 ); + + if ( numI < numIp1 ) + { + iTeam = i; + } + else if ( numI > numIp1 ) + { + iTeam = i + 1; + } + } + + if ( iTeam == -1 ) + { + iTeam = RandomInt( 0, m_nTotalTeams - 1 ); + } + + return iTeam; +} + +//----------------------------------------------------------------------------- +// Purpose: Switch this client to the next team with open slots +//----------------------------------------------------------------------------- +void CMatchmaking::SwitchToNextOpenTeam( CClientInfo *pClient ) +{ + int oldTeam = pClient->m_iTeam[0]; + int maxPlayersPerTeam = m_nGameSize / m_nTotalTeams + 3; + + // Choose the next team for this client + int iTeam = oldTeam; + do + { + iTeam = (iTeam + 1) % m_nTotalTeams; + if ( CountPlayersOnTeam( iTeam ) < maxPlayersPerTeam ) + break; + + } while( iTeam != oldTeam ); + + MM_ClientInfo info; + ClientInfoToNetMessage( &info, pClient ); + for ( int i = 0; i < pClient->m_cPlayers; ++i ) + { + info.m_iTeam[i] = iTeam; + } + + // Send to ourselves and everyone else + ProcessClientInfo( &info ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if the teams are full enough to start +//----------------------------------------------------------------------------- +int CMatchmaking::GetPlayersNeeded() +{ + // System link can always be started + if ( m_Session.IsSystemLink() ) + return 0; + + // check if we can start the game + int total = 0; + + for ( int i = 0; i < m_nTotalTeams; ++i ) + { + total += CountPlayersOnTeam( i ); + } + + + return max( 0, mm_minplayers.GetInt() - max( 0, total ) ); +} |