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
797 lines
23 KiB
//========= 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 ) ); |
|
}
|
|
|