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.
798 lines
23 KiB
798 lines
23 KiB
5 years ago
|
//========= 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 ) );
|
||
|
}
|