You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2364 lines
59 KiB
2364 lines
59 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 "proto_oob.h" |
|
#include "matchmaking.h" |
|
#include "matchmakingqos.h" |
|
#include "Session.h" |
|
#include "vgui_baseui_interface.h" |
|
#include "cdll_engine_int.h" |
|
#include "convar.h" |
|
#include "cmd.h" |
|
#include "iclient.h" |
|
#include "server.h" |
|
#include "host.h" |
|
|
|
#if defined( _X360 ) |
|
#include "xbox/xbox_win32stubs.h" |
|
#include "audio/private/snd_dev_xaudio.h" |
|
#include "audio_pch.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
static CMatchmaking s_Matchmaking; |
|
CMatchmaking *g_pMatchmaking = &s_Matchmaking; |
|
|
|
extern IVEngineClient *engineClient; |
|
extern IXboxSystem *g_pXboxSystem; |
|
|
|
// Expose an interface for GameUI |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMatchmaking, IMatchmaking, VENGINE_MATCHMAKING_VERSION, s_Matchmaking ); |
|
|
|
bool Channel_LessFunc( const uint &a, const uint &b ) |
|
{ |
|
return a < b; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructor |
|
//----------------------------------------------------------------------------- |
|
CMatchmaking::CMatchmaking() : m_Channels( Channel_LessFunc ) |
|
{ |
|
m_bPreventFullServerStartup = false; |
|
m_bCleanup = false; |
|
m_bEnteredLobby = false; |
|
m_nTotalTeams = 0; |
|
m_pSearchResults = NULL; |
|
m_pSessionKeys = new KeyValues( "SessionKeys" ); |
|
|
|
m_Session.SetParent( this ); |
|
|
|
m_CurrentState = MMSTATE_INITIAL; |
|
m_InviteState = INVITE_NONE; |
|
|
|
memset( &m_InviteWaitingInfo, 0, sizeof( m_InviteWaitingInfo ) ); |
|
} |
|
|
|
CMatchmaking::~CMatchmaking() |
|
{ |
|
Cleanup(); |
|
m_pSessionKeys->deleteThis(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Cleanup the matchmaking class to enable re-entry |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::Cleanup() |
|
{ |
|
m_bInitialized = false; |
|
m_bCleanup = false; |
|
m_bEnteredLobby = false; |
|
|
|
m_Host.Clear(); |
|
|
|
#ifdef _X360 |
|
if ( Audio_GetXVoice() ) |
|
{ |
|
CClientInfo *pLocal = NULL; |
|
|
|
if ( m_bCreatedLocalTalker ) |
|
{ |
|
pLocal = &m_Local; |
|
} |
|
|
|
Audio_GetXVoice()->RemoveAllTalkers( pLocal ); |
|
} |
|
#endif |
|
m_bCreatedLocalTalker = false; |
|
SetPreventFullServerStartup( false, "Cleanup\n" ); |
|
|
|
m_Session.ResetSession(); |
|
|
|
// TODO: Check on overlapped operations and cancel them |
|
// g_pXboxSystem->CancelAsyncOperations(); |
|
|
|
ClearSearchResults(); |
|
m_pSessionKeys->Clear(); |
|
|
|
m_pGameServer = NULL; |
|
|
|
Q_memset( m_Mutelist, 0, sizeof( m_Mutelist ) ); |
|
for ( int i = 0; i < MAX_PLAYERS_PER_CLIENT; ++i ) |
|
{ |
|
m_MutedBy[i].Purge(); |
|
} |
|
|
|
m_Channels.RemoveAll(); |
|
|
|
m_SessionContexts.Purge(); |
|
m_SessionProperties.Purge(); |
|
m_PlayerStats.Purge(); |
|
m_Remote.PurgeAndDeleteElements(); |
|
|
|
m_nGameSize = 0; |
|
m_nPrivateSlots = 0; |
|
m_nSendCount = 0; |
|
|
|
m_fHeartbeatInterval = HEARTBEAT_INTERVAL_SHORT; |
|
m_fNextHeartbeatTime = GetTime(); |
|
} |
|
|
|
int CMatchmaking::FindOrCreateContext( const uint id ) |
|
{ |
|
int idx = m_SessionContexts.InvalidIndex(); |
|
for ( int i = 0; i < m_SessionContexts.Count(); ++i ) |
|
{ |
|
if ( m_SessionContexts[i].dwContextId == id ) |
|
{ |
|
idx = i; |
|
} |
|
} |
|
if ( !m_SessionContexts.IsValidIndex( idx ) ) |
|
{ |
|
idx = m_SessionContexts.AddToTail(); |
|
} |
|
return idx; |
|
} |
|
|
|
int CMatchmaking::FindOrCreateProperty( const uint id ) |
|
{ |
|
int idx = m_SessionProperties.InvalidIndex(); |
|
for ( int i = 0; i < m_SessionProperties.Count(); ++i ) |
|
{ |
|
if ( m_SessionProperties[i].dwPropertyId == id ) |
|
{ |
|
idx = i; |
|
} |
|
} |
|
if ( !m_SessionProperties.IsValidIndex( idx ) ) |
|
{ |
|
idx = m_SessionProperties.AddToTail(); |
|
} |
|
return idx; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add an additional property to the current session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::AddSessionProperty( const uint nType, const char *pID, const char *pValue, const char *pValueType ) |
|
{ |
|
KeyValues *pProperty = m_pSessionKeys->FindKey( pID, true ); |
|
pProperty->SetName( pID ); |
|
pProperty->SetInt( "type", nType ); |
|
pProperty->SetString( "valuestring", pValue ); |
|
pProperty->SetString( "valuetype", pValueType ); |
|
|
|
AddSessionPropertyInternal( pProperty ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set properties and contexts for the current session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SetSessionProperties( KeyValues *pPropertyKeys ) |
|
{ |
|
m_SessionContexts.RemoveAll(); |
|
m_SessionProperties.RemoveAll(); |
|
|
|
m_pSessionKeys->Clear(); |
|
pPropertyKeys->CopySubkeys( m_pSessionKeys ); |
|
|
|
for ( KeyValues *pProperty = m_pSessionKeys->GetFirstSubKey(); pProperty != NULL; pProperty = pProperty->GetNextKey() ) |
|
{ |
|
AddSessionPropertyInternal( pProperty ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::AddSessionPropertyInternal( KeyValues *pProperty ) |
|
{ |
|
const char *pID = pProperty->GetName(); |
|
const char *pValue = pProperty->GetString( "valuestring" ); |
|
|
|
switch( pProperty->GetInt( "type" ) ) |
|
{ |
|
case SESSION_CONTEXT: |
|
{ |
|
Msg( "Adding Context: %s : %s\n", pID, pValue ); |
|
|
|
int id = g_ClientDLL->GetPresenceID( pID ); |
|
int val = g_ClientDLL->GetPresenceID( pValue ); |
|
|
|
int idx = FindOrCreateContext( id ); |
|
XUSER_CONTEXT &ctx = m_SessionContexts[idx]; |
|
ctx.dwContextId = id; |
|
ctx.dwValue = val; |
|
|
|
// Set the display string for gameUI |
|
char szBuffer[MAX_PATH]; |
|
g_ClientDLL->GetPropertyDisplayString( ctx.dwContextId, ctx.dwValue, szBuffer, sizeof( szBuffer ) ); |
|
pProperty->SetString( "displaystring", szBuffer ); |
|
|
|
// X360TBD: Such game specifics as this shouldn't be hard-coded |
|
if ( m_CurrentState != MMSTATE_INITIAL && !Q_stricmp( pID, "CONTEXT_SCENARIO" ) ) |
|
{ |
|
// Set the scenario in our host data structure |
|
Q_strncpy( m_HostData.scenario, szBuffer, sizeof( m_HostData.scenario ) ); |
|
UpdateSessionReplyData( XNET_QOS_LISTEN_SET_DATA ); |
|
} |
|
} |
|
break; |
|
|
|
case SESSION_PROPERTY: |
|
{ |
|
Msg( "Adding Property: %s : %s\n", pID, pValue ); |
|
|
|
if ( !Q_stricmp( pID, "PROPERTY_PRIVATE_SLOTS" ) ) |
|
{ |
|
// "Private Slots" is not a search criteria |
|
m_nPrivateSlots = atoi( pValue ); |
|
break; |
|
} |
|
|
|
int id = g_ClientDLL->GetPresenceID( pID ); |
|
|
|
int idx = FindOrCreateProperty( id ); |
|
XUSER_PROPERTY &prop = m_SessionProperties[idx]; |
|
prop.dwPropertyId = id; |
|
|
|
if ( !Q_stricmp( pProperty->GetString( "valuetype" ), "int" ) ) |
|
{ |
|
prop.value.nData = atoi( pValue ); |
|
prop.value.type = XUSER_DATA_TYPE_INT32; |
|
} |
|
|
|
// Build out the property keyvalues for gameUI |
|
char szBuffer[MAX_PATH]; |
|
g_ClientDLL->GetPropertyDisplayString( prop.dwPropertyId, prop.value.nData, szBuffer, sizeof( szBuffer ) ); |
|
pProperty->SetString( "displaystring", szBuffer ); |
|
|
|
// X360TBD: Such game specifics as these shouldn't be so hard-coded |
|
if ( !Q_stricmp( pID, "PROPERTY_GAME_SIZE" ) ) |
|
{ |
|
m_nGameSize = atoi( pValue ); |
|
} |
|
if ( !Q_stricmp( pID, "PROPERTY_NUMBER_OF_TEAMS" ) ) |
|
{ |
|
m_nTotalTeams = atoi( pValue ); |
|
} |
|
if ( m_CurrentState != MMSTATE_INITIAL && !Q_stricmp( pID, "PROPERTY_MAX_GAME_TIME" ) ) |
|
{ |
|
// Set the game time in our host data structure |
|
m_HostData.gameTime = prop.value.nData; |
|
UpdateSessionReplyData( XNET_QOS_LISTEN_SET_DATA ); |
|
} |
|
} |
|
break; |
|
|
|
case SESSION_FLAG: |
|
m_Session.SetFlag( g_ClientDLL->GetPresenceID( pID ) ); |
|
break; |
|
|
|
default: |
|
Warning( "Session option type %d not recognized/n", pProperty->GetInt( "type" ) ); |
|
break; |
|
|
|
} |
|
} |
|
|
|
KeyValues *CMatchmaking::GetSessionProperties() |
|
{ |
|
return m_pSessionKeys; |
|
} |
|
|
|
double CMatchmaking::GetTime() |
|
{ |
|
return Plat_FloatTime(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: At netchannel connection, register the messages |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::ConnectionStart( INetChannel *chan ) |
|
{ |
|
REGISTER_MM_MSG( JoinResponse ); |
|
REGISTER_MM_MSG( ClientInfo ); |
|
REGISTER_MM_MSG( RegisterResponse ); |
|
REGISTER_MM_MSG( Migrate ); |
|
REGISTER_MM_MSG( Mutelist ); |
|
REGISTER_MM_MSG( Checkpoint ); |
|
REGISTER_MM_MSG( Heartbeat ); |
|
|
|
REGISTER_CLC_MSG( VoiceData ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Process a networked voice packet |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::ProcessVoiceData( CLC_VoiceData *pVoice ) |
|
{ |
|
char chReceived[4096]; |
|
DWORD dwLength = pVoice->m_nLength; |
|
pVoice->m_DataIn.ReadBits( chReceived, dwLength ); |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
char chCopyBuffer[4096]; |
|
|
|
// Forward this message on to everyone else |
|
pVoice->m_DataOut.StartWriting( chCopyBuffer, sizeof ( chCopyBuffer ) ); |
|
Q_memcpy( chCopyBuffer, chReceived, sizeof( chCopyBuffer ) ); |
|
pVoice->m_DataOut.SeekToBit( dwLength ); |
|
|
|
SendToRemoteClients( pVoice, true, pVoice->m_xuid ); |
|
} |
|
|
|
// Playback the voice data locally through xaudio |
|
#if defined ( _X360 ) |
|
if ( pVoice->m_xuid != m_Local.m_xuids[0] ) |
|
{ |
|
Audio_GetXVoice()->PlayIncomingVoiceData( pVoice->m_xuid, (byte*)chReceived, dwLength ); |
|
} |
|
#endif |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Delete channels that have been marked for deletion |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::CleanupMarkedChannels() |
|
{ |
|
// Clean up net channels that need to be deleted |
|
for ( int i = 0; i < m_ChannelsToRemove.Count(); ++i ) |
|
{ |
|
INetChannel *pNetChannel = FindChannel( m_ChannelsToRemove[i] ); |
|
if ( pNetChannel ) |
|
{ |
|
if ( !m_Channels.Remove( m_ChannelsToRemove[i] ) ) |
|
{ |
|
Warning( "CleanupMarkedChannels: Failed to remove a channel!\n" ); |
|
} |
|
} |
|
} |
|
m_ChannelsToRemove.Purge(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Channels can be flagged for deletion during packet processing. |
|
// Now that processing is finished, delete them. |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::PacketEnd() |
|
{ |
|
CleanupMarkedChannels(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a specific client from a netchannel |
|
//----------------------------------------------------------------------------- |
|
CClientInfo *CMatchmaking::FindClient( netadr_t *adr ) |
|
{ |
|
CClientInfo *pClient = NULL; |
|
unsigned int ip = adr->GetIPNetworkByteOrder(); |
|
|
|
if ( ip == m_Host.m_adr.GetIPNetworkByteOrder() ) |
|
{ |
|
pClient = &m_Host; |
|
} |
|
else |
|
{ |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
if ( ip == m_Remote[i]->m_adr.GetIPNetworkByteOrder() ) |
|
{ |
|
pClient = m_Remote[i]; |
|
break; |
|
} |
|
} |
|
} |
|
return pClient; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a specific client by his XUID |
|
//----------------------------------------------------------------------------- |
|
CClientInfo *CMatchmaking::FindClientByXUID( XUID xuid ) |
|
{ |
|
CClientInfo *pClient = NULL; |
|
|
|
if ( xuid == m_Host.m_xuids[0] ) |
|
{ |
|
pClient = &m_Host; |
|
} |
|
else |
|
{ |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
if ( xuid == m_Remote[i]->m_xuids[0] ) |
|
{ |
|
pClient = m_Remote[i]; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return pClient; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find a specific client's netchannel |
|
//----------------------------------------------------------------------------- |
|
INetChannel *CMatchmaking::FindChannel( const unsigned int ip ) |
|
{ |
|
INetChannel *pChannel = NULL; |
|
|
|
int idx = m_Channels.Find( ip ); |
|
if ( idx != m_Channels.InvalidIndex() ) |
|
{ |
|
pChannel = m_Channels.Element( idx ); |
|
} |
|
|
|
return pChannel; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Create a new netchannel |
|
//----------------------------------------------------------------------------- |
|
INetChannel *CMatchmaking::CreateNetChannel( netadr_t *adr ) |
|
{ |
|
INetChannel *pNewChannel = FindChannel( adr->GetIPNetworkByteOrder() ); |
|
if ( !pNewChannel ) |
|
{ |
|
pNewChannel = NET_CreateNetChannel( NS_MATCHMAKING, adr, "MATCHMAKING", this ); |
|
} |
|
|
|
if( pNewChannel ) |
|
{ |
|
// Set a rate limit and other relevant properties |
|
pNewChannel->SetTimeout( HEARTBEAT_TIMEOUT ); |
|
} |
|
|
|
return pNewChannel; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add a netchannel for a session client |
|
//----------------------------------------------------------------------------- |
|
INetChannel *CMatchmaking::AddRemoteChannel( netadr_t *adr ) |
|
{ |
|
INetChannel *pNetChannel = CreateNetChannel( adr ); |
|
if ( pNetChannel ) |
|
{ |
|
// Save this new channel |
|
m_Channels.Insert( adr->GetIPNetworkByteOrder(), pNetChannel ); |
|
} |
|
return pNetChannel; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove a netchannel for a session client |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::RemoveRemoteChannel( netadr_t *adr, const char *pReason ) |
|
{ |
|
INetChannel *pNetChannel = FindChannel( adr->GetIPNetworkByteOrder() ); |
|
if ( pNetChannel ) |
|
{ |
|
m_Channels.Remove( adr->GetIPNetworkByteOrder() ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Mark a net channel to be removed |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::MarkChannelForRemoval( netadr_t *adr ) |
|
{ |
|
m_ChannelsToRemove.AddToTail( adr->GetIPNetworkByteOrder() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set the timeout for a net channel |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SetChannelTimeout( netadr_t *adr, int timeout ) |
|
{ |
|
INetChannel *pChannel = FindChannel( adr->GetIPNetworkByteOrder() ); |
|
if ( pChannel ) |
|
{ |
|
Msg( "Setting new timeout for ip %d: %d\n", adr->GetIPNetworkByteOrder(), timeout ); |
|
pChannel->SetTimeout( timeout ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send a net message to a specific address |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SendMessage( INetMessage *msg, netadr_t *adr, bool bVoice ) |
|
{ |
|
// Find the matching net channel |
|
INetChannel *pChannel = FindChannel( adr->GetIPNetworkByteOrder() ); |
|
if ( pChannel ) |
|
{ |
|
pChannel->SendNetMsg( *msg, false, bVoice ); |
|
if ( !pChannel->Transmit() ) |
|
{ |
|
Msg( "Transmit failed\n" ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send a net message to a specific client |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SendMessage( INetMessage *msg, CClientInfo *pClient, bool bVoice ) |
|
{ |
|
// Find the matching net channel |
|
INetChannel *pChannel = FindChannel( pClient->m_adr.GetIPNetworkByteOrder() ); |
|
if ( pChannel ) |
|
{ |
|
pChannel->SendNetMsg( *msg, false, bVoice ); |
|
if ( !pChannel->Transmit() ) |
|
{ |
|
Msg( "Transmit failed\n" ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send a net message to all remote clients |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SendToRemoteClients( INetMessage *msg, bool bVoice, XUID excludeXUID ) |
|
{ |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
CClientInfo *pInfo = m_Remote[i]; |
|
|
|
if ( excludeXUID != -1 && pInfo->m_xuids[0] == excludeXUID ) |
|
continue; |
|
|
|
SendMessage( msg, m_Remote[i], true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send a heartbeat to a specific client |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::SendHeartbeat( CClientInfo *pClient ) |
|
{ |
|
if ( pClient->m_adr.GetIPNetworkByteOrder() == 0 ) |
|
return false; |
|
|
|
// Check for timeout |
|
INetChannel *pChannel = FindChannel( pClient->m_adr.GetIPNetworkByteOrder() ); |
|
if ( pChannel ) |
|
{ |
|
// Msg( "Sending HB\n" ); |
|
|
|
if ( pChannel->IsTimedOut() ) |
|
{ |
|
ClientDropped( pClient ); |
|
return false; |
|
} |
|
|
|
// Send a heartbeat to the client |
|
MM_Heartbeat beat; |
|
pChannel->SendNetMsg( beat ); |
|
pChannel->Transmit(); |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Transmit regular messages to keep the connection alive |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SendHeartbeat() |
|
{ |
|
double time = GetTime(); |
|
if ( time < m_fNextHeartbeatTime ) |
|
return; |
|
|
|
m_fNextHeartbeatTime = time + m_fHeartbeatInterval; |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
SendHeartbeat( m_Remote[i] ); |
|
} |
|
} |
|
else |
|
{ |
|
SendHeartbeat( &m_Host ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Look up a player by name |
|
//----------------------------------------------------------------------------- |
|
static uint64 FindPlayerByName( CClientInfo *pClient, const char *pName ) |
|
{ |
|
for ( int i = 0; i < XUSER_MAX_COUNT; ++i ) |
|
{ |
|
if ( pClient->m_xuids[i] && !Q_stricmp( pClient->m_szGamertags[i], pName ) ) |
|
{ |
|
return pClient->m_xuids[i]; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get an xuid from a CBasePlayer id |
|
//----------------------------------------------------------------------------- |
|
uint64 CMatchmaking::PlayerIdToXuid( int playerId ) |
|
{ |
|
uint64 ret = 0; |
|
|
|
player_info_t info; |
|
if ( engineClient->GetPlayerInfo( playerId, &info ) ) |
|
{ |
|
// find the client with a matching name |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
ret = FindPlayerByName( m_Remote[i], info.name ); |
|
if ( ret ) |
|
{ |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( !ret ) |
|
{ |
|
// Try ourselves |
|
ret = FindPlayerByName( &m_Local, info.name ); |
|
} |
|
|
|
if ( !ret ) |
|
{ |
|
// Try the host |
|
ret = FindPlayerByName( &m_Host, info.name ); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
bool CMatchmaking::GameIsActive() |
|
{ |
|
return m_CurrentState > MMSTATE_GAME_ACTIVE; |
|
} |
|
|
|
bool CMatchmaking::GameIsLocked() |
|
{ |
|
return ( m_Session.IsArbitrated() && m_CurrentState > MMSTATE_GAME_LOCKED ); |
|
} |
|
|
|
bool CMatchmaking::ConnectedToServer() |
|
{ |
|
return engineClient->IsConnected(); |
|
} |
|
|
|
bool CMatchmaking::IsInMigration() |
|
{ |
|
return ( m_CurrentState >= MMSTATE_HOSTMIGRATE_STARTINGMIGRATION && |
|
m_CurrentState <= MMSTATE_HOSTMIGRATE_WAITINGFORHOST ); |
|
} |
|
|
|
bool CMatchmaking::IsAcceptingConnections() |
|
{ |
|
if ( !m_Session.IsHost() || |
|
m_CurrentState < MMSTATE_ACCEPTING_CONNECTIONS || |
|
m_CurrentState == MMSTATE_PREGAME || |
|
m_CurrentState == MMSTATE_LOADING || |
|
GameIsLocked() ) |
|
{ |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool CMatchmaking::IsServer() |
|
{ |
|
// for now, the host is the server |
|
return m_Session.IsHost(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Helpers to convert between CClientInfo and MM_ClientInfo |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::ClientInfoToNetMessage( MM_ClientInfo *pInfo, const CClientInfo *pClient ) |
|
{ |
|
pInfo->m_id = pClient->m_id; |
|
pInfo->m_xnaddr = pClient->m_xnaddr; |
|
pInfo->m_cPlayers = pClient->m_cPlayers; |
|
pInfo->m_bInvited = pClient->m_bInvited; |
|
|
|
Q_memcpy( pInfo->m_xuids, pClient->m_xuids, sizeof( pInfo->m_xuids ) ); |
|
Q_memcpy( pInfo->m_cVoiceState, pClient->m_cVoiceState, sizeof( pInfo->m_cVoiceState ) ); |
|
Q_memcpy( pInfo->m_iTeam, pClient->m_iTeam, sizeof( pInfo->m_iTeam ) ); |
|
Q_memcpy( pInfo->m_iControllers, pClient->m_iControllers, sizeof( pInfo->m_iControllers ) ); |
|
Q_memcpy( pInfo->m_szGamertags, pClient->m_szGamertags, sizeof( pInfo->m_szGamertags ) ); |
|
} |
|
|
|
void CMatchmaking::NetMessageToClientInfo( CClientInfo *pClient, const MM_ClientInfo *pInfo ) |
|
{ |
|
pClient->m_id = pInfo->m_id; |
|
pClient->m_xnaddr = pInfo->m_xnaddr; |
|
pClient->m_cPlayers = pInfo->m_cPlayers; |
|
pClient->m_bInvited = pInfo->m_bInvited; |
|
|
|
#if defined( _X360 ) |
|
IN_ADDR winaddr; |
|
XNKID xid = m_Session.GetSessionId(); |
|
if ( XNetXnAddrToInAddr( &pClient->m_xnaddr, &xid, &winaddr ) != 0 ) |
|
{ |
|
Warning( "Error resolving client IP\n" ); |
|
} |
|
pClient->m_adr.SetType( NA_IP ); |
|
pClient->m_adr.SetIPAndPort( winaddr.S_un.S_addr, PORT_MATCHMAKING ); |
|
#endif |
|
|
|
Q_memcpy( pClient->m_xuids, pInfo->m_xuids, sizeof( pClient->m_xuids ) ); |
|
Q_memcpy( pClient->m_cVoiceState, pInfo->m_cVoiceState, sizeof( pClient->m_cVoiceState ) ); |
|
Q_memcpy( pClient->m_iTeam, pInfo->m_iTeam, sizeof( pClient->m_iTeam ) ); |
|
Q_memcpy( pClient->m_iControllers, pInfo->m_iControllers, sizeof( pClient->m_iControllers ) ); |
|
Q_memcpy( pClient->m_szGamertags, pInfo->m_szGamertags, sizeof( pClient->m_szGamertags ) ); |
|
} |
|
|
|
//---------------------------------------- |
|
// |
|
// Host/Client Shared |
|
// |
|
//---------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set up the properties of the local client machine |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::InitializeLocalClient( bool bIsHost ) |
|
{ |
|
Q_memset( &m_Local, 0, sizeof( m_Local ) ); |
|
|
|
m_Local.m_bInvited = bIsHost; |
|
|
|
#if defined( _X360 ) |
|
while( XNetGetTitleXnAddr( &m_Local.m_xnaddr ) == XNET_GET_XNADDR_PENDING ) |
|
; |
|
|
|
// machine id |
|
if ( 0 != XNetXnAddrToMachineId( &m_Local.m_xnaddr, &m_Local.m_id ) ) |
|
{ |
|
// User isn't signed in to live, use their xuid instead |
|
XUserGetXUID( XBX_GetPrimaryUserId(), &m_Local.m_id ); |
|
} |
|
|
|
m_Local.m_cPlayers = 0; |
|
|
|
// Set up the players |
|
for ( uint i = 0; i < MAX_PLAYERS_PER_CLIENT; ++i ) |
|
{ |
|
// We currently only allow one player per console |
|
if ( i != XBX_GetPrimaryUserId() ) |
|
{ |
|
continue; |
|
} |
|
|
|
// xuid |
|
uint ret = XUserGetXUID( i, &m_Local.m_xuids[m_Local.m_cPlayers] ); |
|
if ( ret == ERROR_NO_SUCH_USER ) |
|
{ |
|
continue; |
|
} |
|
else if ( ret != ERROR_SUCCESS ) |
|
{ |
|
return false; |
|
} |
|
|
|
// gamertag |
|
ret = XUserGetName( XBX_GetPrimaryUserId(), m_Local.m_szGamertags[m_Local.m_cPlayers], MAX_PLAYER_NAME_LENGTH ); |
|
if ( ret != ERROR_SUCCESS ) |
|
{ |
|
return false; |
|
} |
|
m_Local.m_szGamertags[m_Local.m_cPlayers][MAX_PLAYER_NAME_LENGTH - 1] = '\0'; |
|
|
|
// Set the player's name in the game |
|
char szNameCmd[MAX_PLAYER_NAME_LENGTH + 16]; |
|
Q_snprintf( szNameCmd, sizeof( szNameCmd ), "name %s", m_Local.m_szGamertags[m_Local.m_cPlayers] ); |
|
engineClient->ClientCmd( szNameCmd ); |
|
|
|
m_Local.m_iControllers[m_Local.m_cPlayers] = i; |
|
m_Local.m_iTeam[m_Local.m_cPlayers] = -1; |
|
|
|
|
|
m_Local.m_cVoiceState[m_Local.m_cPlayers] = 0; |
|
|
|
// number of players on this console |
|
++m_Local.m_cPlayers; |
|
} |
|
|
|
// Source can only support one player per console. |
|
if( m_Local.m_cPlayers > 1 ) |
|
{ |
|
Warning( "Too many players on this console\n" ); |
|
return false; |
|
} |
|
|
|
// Set up the host data that gets sent back to searching clients |
|
// By default, the first player is considered the host |
|
Q_strncpy( m_HostData.hostName, m_Local.m_szGamertags[0], sizeof( m_HostData.hostName ) ); |
|
m_HostData.gameState = GAMESTATE_INLOBBY; |
|
m_HostData.xuid = m_Local.m_xuids[ XBX_GetPrimaryUserId() ]; |
|
|
|
#endif |
|
|
|
m_bInitialized = true; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Connection to the game server has been established, so we can |
|
// add the local players to the teams that were setup in the lobby. |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::AddLocalPlayersToTeams() |
|
{ |
|
if ( !m_bInitialized || XBX_GetPrimaryUserId() == INVALID_USER_ID ) |
|
return; |
|
|
|
if ( m_Local.m_iTeam[0] == -1 ) |
|
return; |
|
|
|
// Convert the team number into a team name |
|
char szTeamName[32]; |
|
uint id = g_ClientDLL->GetPresenceID( "PROPERTY_TEAM" ); |
|
g_ClientDLL->GetPropertyDisplayString( id, m_Local.m_iTeam[0], szTeamName, sizeof( szTeamName ) ); |
|
|
|
Msg( "Joining team: %s\n", szTeamName ); |
|
|
|
char cmd[32]; |
|
Q_snprintf( cmd, sizeof( cmd ), "jointeam_nomenus %s", szTeamName ); |
|
engineClient->ClientCmd( cmd ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Map loading is completed - restore full communication |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::OnLevelLoadingFinished() |
|
{ |
|
// This functions gets called from some odd places |
|
if ( m_CurrentState != MMSTATE_CONNECTED_TO_SERVER ) |
|
return; |
|
|
|
// Test code to force a disconnect at end of map load |
|
// if ( !IsServer() ) |
|
// { |
|
// char cmd[MAX_PATH]; |
|
// Q_snprintf( cmd, sizeof( cmd ), "connect 127.0.0.1\n" ); |
|
// Cbuf_AddText( cmd ); |
|
// return; |
|
// } |
|
|
|
SwitchToState( MMSTATE_INGAME ); |
|
|
|
MM_Checkpoint msg; |
|
msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_LOADING_COMPLETE; |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
if ( !m_Session.IsArbitrated() ) |
|
{ |
|
// Re-enable response to probes |
|
m_HostData.gameState = GAMESTATE_INPROGRESS; |
|
UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA ); |
|
} |
|
|
|
// Reset netchannel timeouts for any clients that are also finished loading |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
CClientInfo *pClient = m_Remote[i]; |
|
if ( pClient->m_bLoaded ) |
|
{ |
|
// Send a reply and reset the netchannel timeout |
|
SendMessage( &msg, pClient ); |
|
SetChannelTimeout( &pClient->m_adr, HEARTBEAT_TIMEOUT ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Tell the host we're finished loading |
|
SendMessage( &msg, &m_Host ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Process packet from another client |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::ProcessConnectionlessPacket( netpacket_t *pPacket ) |
|
{ |
|
Assert( pPacket ); |
|
|
|
bf_read &msg = pPacket->message; |
|
int type = msg.ReadByte(); |
|
switch( type ) |
|
{ |
|
case PTH_SYSTEMLINK_SEARCH: |
|
HandleSystemLinkSearch( pPacket ); |
|
break; |
|
|
|
case HTP_SYSTEMLINK_REPLY: |
|
HandleSystemLinkReply( pPacket ); |
|
break; |
|
|
|
case PTH_CONNECT: |
|
HandleJoinRequest( pPacket ); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Process an info update about another client |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::ProcessClientInfo( MM_ClientInfo *pInfo ) |
|
{ |
|
CClientInfo *pClient = NULL; |
|
bool bHost = false; |
|
|
|
if ( m_CurrentState == MMSTATE_INITIAL ) |
|
{ |
|
// Session has been reset, this is a stale message |
|
Msg( "Received MM_ClientInfo with MMSTATE_INITIAL\n" ); |
|
return true; |
|
} |
|
|
|
if ( pInfo->m_id == m_Local.m_id ) |
|
{ |
|
if ( pInfo->m_cPlayers == 0 ) |
|
{ |
|
if ( m_CurrentState != MMSTATE_SESSION_DISCONNECTING ) |
|
{ |
|
// We've been kicked |
|
KickPlayerFromSession( 0 ); |
|
SessionNotification( SESSION_NOTIFY_CLIENT_KICKED ); |
|
} |
|
return true; |
|
} |
|
else |
|
{ |
|
pClient = &m_Local; |
|
} |
|
} |
|
|
|
// Check against our host id |
|
if ( pInfo->m_id == m_Host.m_id ) |
|
{ |
|
pClient = &m_Host; |
|
bHost = true; |
|
} |
|
else |
|
{ |
|
// Look for the client in our remote list |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
if ( m_Remote[i]->m_id == pInfo->m_id ) |
|
{ |
|
pClient = m_Remote[i]; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// If we didn't find it, this must be a new client |
|
if ( !pClient && pInfo->m_cPlayers != 0 ) |
|
{ |
|
Msg( "New client. %s\n", pInfo->ToString() ); |
|
|
|
pClient = new CClientInfo(); |
|
m_Remote.AddToTail( pClient ); |
|
|
|
// Copy the new client info |
|
NetMessageToClientInfo( pClient, pInfo ); |
|
|
|
AddPlayersToSession( pClient ); |
|
SendPlayerInfoToLobby( pClient ); |
|
} |
|
else |
|
{ |
|
// We're updating an existing client |
|
if ( pInfo->m_cPlayers ) |
|
{ |
|
// Cache off the old client info, as pClient gets updated through this function |
|
CClientInfo tempClient = *pClient; |
|
|
|
// Check for player changes |
|
if ( Q_memcmp( &tempClient.m_xuids, pInfo->m_xuids, sizeof( tempClient.m_xuids ) ) ) |
|
{ |
|
// Remove the old players and add the new |
|
RemovePlayersFromSession( pClient ); |
|
NetMessageToClientInfo( pClient, pInfo ); |
|
AddPlayersToSession( pClient ); |
|
} |
|
|
|
// Check for team changes |
|
for ( int i = 0; i < pInfo->m_cPlayers; ++i ) |
|
{ |
|
if ( pInfo->m_iTeam[i] != tempClient.m_iTeam[i] || pInfo->m_cVoiceState[i] != tempClient.m_cVoiceState[i] ) |
|
{ |
|
// X360TBD: send real "ready" setting, or remove entirely? |
|
EngineVGui()->UpdatePlayerInfo( pInfo->m_xuids[i], pInfo->m_szGamertags[i], pInfo->m_iTeam[i], pInfo->m_cVoiceState[i], GetPlayersNeeded(), bHost ); |
|
} |
|
} |
|
|
|
// Store the new info |
|
NetMessageToClientInfo( pClient, pInfo ); |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
SendToRemoteClients( pInfo ); |
|
} |
|
} |
|
else |
|
{ |
|
// A client has been dropped |
|
ClientDropped( pClient ); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A connection was lost - respond accordingly |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::ClientDropped( CClientInfo *pClient ) |
|
{ |
|
if ( !pClient ) |
|
{ |
|
Warning( "Null client pointer in ClientDropped!\n" ); |
|
return; |
|
} |
|
|
|
if ( m_CurrentState == MMSTATE_SESSION_CONNECTING ) |
|
{ |
|
// Not really dropped, we just failed to connect to the host |
|
SessionNotification( SESSION_NOTIFY_CONNECT_NOTAVAILABLE ); |
|
return; |
|
} |
|
|
|
Warning( "Dropped player: %llu!", pClient->m_id ); |
|
|
|
// Do this first, before the team assignment gets cleared |
|
RemovePlayersFromSession( pClient ); |
|
|
|
// Remove all players from the lobby |
|
for ( int i = 0; i < pClient->m_cPlayers; ++i ) |
|
{ |
|
pClient->m_iTeam[i] = -1; |
|
} |
|
SendPlayerInfoToLobby( pClient ); |
|
|
|
MarkChannelForRemoval( &pClient->m_adr ); |
|
|
|
if ( pClient == &m_Host ) |
|
{ |
|
// The host was lost |
|
if ( m_Session.IsSystemLink() ) |
|
{ |
|
// Can't migrate system link sessions |
|
SessionNotification( SESSION_NOTIFY_LOST_HOST ); |
|
} |
|
else |
|
{ |
|
// X360TBD: Migration still doesn't work correctly |
|
SessionNotification( SESSION_NOTIFY_LOST_HOST ); |
|
/* |
|
// Start migrating |
|
if ( !IsInMigration() ) |
|
{ |
|
m_PreMigrateState = m_CurrentState; |
|
StartHostMigration(); |
|
} |
|
*/ |
|
} |
|
} |
|
else |
|
{ |
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Send a disconnect ack back to the client |
|
MM_Checkpoint msg; |
|
msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_SESSION_DISCONNECT; |
|
SendMessage( &msg, &pClient->m_adr ); |
|
|
|
// Tell everyone else this client is gone |
|
MM_ClientInfo droppedPlayer; |
|
ClientInfoToNetMessage( &droppedPlayer, pClient ); |
|
droppedPlayer.m_cPlayers = 0; |
|
|
|
SendToRemoteClients( &droppedPlayer ); |
|
|
|
for ( int i = 0; i < sv.GetClientCount(); i++ ) |
|
{ |
|
IClient *pIClient = sv.GetClient(i); |
|
bool bFound = false; |
|
|
|
if ( pIClient ) |
|
{ |
|
for ( int j = 0; j < pClient->m_cPlayers; ++j ) |
|
{ |
|
if ( pClient->m_xuids[j] == 0 ) |
|
continue; |
|
|
|
if ( Q_stricmp( pIClient->GetClientName(), pClient->m_szGamertags[j] ) == 0 ) |
|
{ |
|
bFound = true; |
|
pIClient->Disconnect( "Timed Out" ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( bFound == true ) |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( m_Remote.FindAndRemove( pClient ) ) |
|
{ |
|
delete pClient; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A player is leaving the session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::PerformDisconnect() |
|
{ |
|
if ( m_CurrentState == MMSTATE_SESSION_DISCONNECTING ) |
|
{ |
|
if ( ConnectedToServer() ) |
|
{ |
|
engineClient->ExecuteClientCmd( "disconnect" ); |
|
EngineVGui()->ActivateGameUI(); |
|
} |
|
|
|
if ( m_bEnteredLobby ) |
|
{ |
|
EngineVGui()->SessionNotification( SESSION_NOTIFY_WELCOME ); |
|
} |
|
SwitchToState( MMSTATE_INITIAL ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A player is leaving the session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::KickPlayerFromSession( uint64 id ) |
|
{ |
|
MM_ClientInfo droppedPlayer; |
|
|
|
if ( m_Local.m_xuids[0] == id || id == 0 ) |
|
{ |
|
// We've been kicked, or voluntarily left the session |
|
ClientInfoToNetMessage( &droppedPlayer, &m_Local ); |
|
droppedPlayer.m_cPlayers = 0; |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Tell all the clients |
|
SendToRemoteClients( &droppedPlayer ); |
|
} |
|
else |
|
{ |
|
// tell the host to drop us |
|
SendMessage( &droppedPlayer, &m_Host ); |
|
} |
|
|
|
// Prepare to close the session and reset |
|
SwitchToState( MMSTATE_SESSION_DISCONNECTING ); |
|
} |
|
else if ( m_Session.IsHost() ) |
|
{ |
|
// Host wants to kick a client |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
CClientInfo *pClient = m_Remote[i]; |
|
for ( int j = 0; j < pClient->m_cPlayers; ++j ) |
|
{ |
|
if ( pClient->m_xuids[j] == id ) |
|
{ |
|
ClientInfoToNetMessage( &droppedPlayer, pClient ); |
|
droppedPlayer.m_cPlayers = 0; |
|
SendMessage( &droppedPlayer, pClient ); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add players to the session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::AddPlayersToSession( CClientInfo *pClient ) |
|
{ |
|
bool bIsLocal = false; |
|
|
|
if ( &m_Local == pClient ) |
|
{ |
|
m_Session.JoinLocal( pClient ); |
|
bIsLocal = true; |
|
} |
|
else |
|
{ |
|
m_Session.JoinRemote( pClient ); |
|
} |
|
|
|
#if defined ( _X360 ) |
|
if ( Audio_GetXVoice() ) |
|
{ |
|
Audio_GetXVoice()->AddPlayerToVoiceList( pClient, bIsLocal ); |
|
if ( bIsLocal ) |
|
{ |
|
m_bCreatedLocalTalker = true; |
|
} |
|
} |
|
#endif |
|
UpdateMuteList(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Remove players from the session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::RemovePlayersFromSession( CClientInfo *pClient ) |
|
{ |
|
if ( !pClient->m_cPlayers ) |
|
return; |
|
|
|
bool bIsLocal = false; |
|
|
|
if ( &m_Local == pClient ) |
|
{ |
|
m_Session.RemoveLocal( pClient ); |
|
bIsLocal = true; |
|
} |
|
else |
|
{ |
|
m_Session.RemoveRemote( pClient ); |
|
} |
|
|
|
#if defined ( _X360 ) |
|
if ( Audio_GetXVoice() ) |
|
{ |
|
Audio_GetXVoice()->RemovePlayerFromVoiceList( pClient, bIsLocal ); |
|
} |
|
#endif |
|
UpdateMuteList(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check if a client is muted |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::IsPlayerMuted( int iUserId, XUID playerId ) |
|
{ |
|
for ( int i = 0; i < MAX_PLAYERS; ++i ) |
|
{ |
|
if ( m_Mutelist[iUserId][i] == playerId ) |
|
{ |
|
return true; |
|
} |
|
} |
|
if ( m_MutedBy[iUserId].HasElement( playerId ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determine which clients should be muted for the local client |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::UpdateMuteList() |
|
{ |
|
// Process our mute list |
|
MM_Mutelist msg; |
|
GenerateMutelist( &msg ); |
|
|
|
// Send that to everyone else |
|
if ( !m_Session.IsHost() ) |
|
{ |
|
SendMessage( &msg, &m_Host ); |
|
} |
|
else |
|
{ |
|
ProcessMutelist( &msg ); |
|
} |
|
} |
|
|
|
void Con_PrintTalkers( const CCommand &args ) |
|
{ |
|
g_pMatchmaking->PrintVoiceStatus(); |
|
} |
|
|
|
void CMatchmaking::PrintVoiceStatus( void ) |
|
{ |
|
#ifdef _X360 |
|
int iRemoteClient = 0; |
|
int numRemoteTalkers; |
|
XUID remoteTalkers[MAX_PLAYERS]; |
|
Audio_GetXVoice()->GetRemoteTalkers( &numRemoteTalkers, remoteTalkers ); |
|
|
|
CClientInfo *pClient = &m_Local; |
|
|
|
if ( m_Session.IsHost() == false ) |
|
{ |
|
pClient = &m_Host; |
|
} |
|
|
|
|
|
Msg( "Num Remote Talkers: %d\n", numRemoteTalkers ); |
|
|
|
bool bFound = false; |
|
|
|
while ( pClient ) |
|
{ |
|
if ( pClient != &m_Local ) |
|
{ |
|
for ( int iRemote = 0; iRemote < numRemoteTalkers; iRemote++ ) |
|
{ |
|
if ( pClient->m_xuids[0] == remoteTalkers[iRemote] ) |
|
{ |
|
bFound = true; |
|
break; |
|
} |
|
} |
|
|
|
if ( bFound == true ) |
|
{ |
|
Msg( "Found a Talker: %s\n", pClient->m_szGamertags[0] ); |
|
} |
|
else |
|
{ |
|
Msg( "ALERT!!! %s not in Talker list\n", pClient->m_szGamertags[0] ); |
|
} |
|
} |
|
|
|
if ( iRemoteClient < m_Remote.Count() ) |
|
{ |
|
pClient = m_Remote[iRemoteClient]; |
|
iRemoteClient++; |
|
} |
|
else |
|
{ |
|
pClient = NULL; |
|
} |
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
static ConCommand voice_printtalkers( "voice_printtalkers", Con_PrintTalkers, "voice debug.", FCVAR_DONTRECORD ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update a client's mute list |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::GenerateMutelist( MM_Mutelist *pMsg ) |
|
{ |
|
#if defined( _X360 ) |
|
// Get our remote talker list |
|
if ( !Audio_GetXVoice() ) |
|
return; |
|
|
|
int numRemoteTalkers; |
|
XUID remoteTalkers[MAX_PLAYERS]; |
|
Audio_GetXVoice()->GetRemoteTalkers( &numRemoteTalkers, remoteTalkers ); |
|
|
|
pMsg->m_cPlayers = 0; |
|
|
|
// Loop through local players and update mutes |
|
for ( int iLocal = 0; iLocal < m_Local.m_cPlayers; ++iLocal ) |
|
{ |
|
pMsg->m_cMuted[iLocal] = 0; |
|
|
|
pMsg->m_xuid[pMsg->m_cPlayers] = m_Local.m_xuids[iLocal]; |
|
|
|
for ( int iRemote = 0; iRemote < numRemoteTalkers; ++iRemote ) |
|
{ |
|
BOOL bIsMuted = false; |
|
|
|
DWORD ret = XUserMuteListQuery( m_Local.m_iControllers[iLocal], remoteTalkers[iRemote], &bIsMuted ); |
|
if( ERROR_SUCCESS != ret ) |
|
{ |
|
Warning( "Warning: XUserMuteListQuery() returned 0x%08x for user %d\n", ret, iLocal ); |
|
} |
|
|
|
if( bIsMuted ) |
|
{ |
|
pMsg->m_Muted[pMsg->m_cPlayers].AddToTail( remoteTalkers[iRemote ] ); |
|
++pMsg->m_cMuted[pMsg->m_cPlayers]; |
|
} |
|
else |
|
{ |
|
bIsMuted = m_MutedBy[iLocal].HasElement( remoteTalkers[iRemote] ); |
|
} |
|
|
|
if( bIsMuted ) |
|
{ |
|
Audio_GetXVoice()->SetPlaybackPriority( remoteTalkers[iRemote], m_Local.m_iControllers[iLocal], XHV_PLAYBACK_PRIORITY_NEVER ); |
|
} |
|
else |
|
{ |
|
Audio_GetXVoice()->SetPlaybackPriority( remoteTalkers[iRemote], m_Local.m_iControllers[iLocal], XHV_PLAYBACK_PRIORITY_MAX ); |
|
} |
|
} |
|
|
|
pMsg->m_cRemoteTalkers[pMsg->m_cPlayers] = m_MutedBy[pMsg->m_cPlayers].Count(); |
|
++pMsg->m_cPlayers; |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle the mutelist from another client |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::ProcessMutelist( MM_Mutelist *pMsg ) |
|
{ |
|
#if defined( _X360 ) |
|
// local players |
|
for( int i = 0; i < m_Local.m_cPlayers; ++i ) |
|
{ |
|
// remote players |
|
for( int j = 0; j < pMsg->m_cPlayers; ++j ) |
|
{ |
|
m_MutedBy[i].FindAndRemove( pMsg->m_xuid[j] ); |
|
|
|
// players muted by remote player |
|
for( int k = 0; k < pMsg->m_cMuted[j]; ++k ) |
|
{ |
|
if( m_Local.m_xuids[i] == pMsg->m_Muted[j][k] ) |
|
{ |
|
m_MutedBy[i].AddToTail( pMsg->m_xuid[j] ); |
|
} |
|
} |
|
|
|
BOOL bIsMuted = m_MutedBy[i].HasElement( pMsg->m_xuid[j] ); |
|
if ( !bIsMuted ) |
|
{ |
|
XUserMuteListQuery( m_Local.m_iControllers[i], pMsg->m_xuid[j], &bIsMuted ); |
|
} |
|
if( bIsMuted ) |
|
{ |
|
Audio_GetXVoice()->SetPlaybackPriority( pMsg->m_xuid[j], m_Local.m_iControllers[i], XHV_PLAYBACK_PRIORITY_NEVER ); |
|
} |
|
else |
|
{ |
|
Audio_GetXVoice()->SetPlaybackPriority( pMsg->m_xuid[j], m_Local.m_iControllers[i], XHV_PLAYBACK_PRIORITY_MAX ); |
|
} |
|
} |
|
} |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Pass this along to everyone else |
|
SendToRemoteClients( pMsg ); |
|
} |
|
|
|
#endif |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: A client is changing to another team |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::ChangeTeam( const char *pTeamName ) |
|
{ |
|
if ( !pTeamName ) |
|
{ |
|
// Automatic switch to next team |
|
if ( m_Session.IsHost() ) |
|
{ |
|
if ( m_CurrentState == MMSTATE_ACCEPTING_CONNECTIONS ) |
|
{ |
|
// Put ourselves on another team and |
|
// tell the other players |
|
SwitchToNextOpenTeam( &m_Local ); |
|
} |
|
} |
|
else |
|
{ |
|
// Send a request to the host |
|
MM_Checkpoint msg; |
|
msg.m_Checkpoint = MM_Checkpoint::CHECKPOINT_CHANGETEAM; |
|
SendMessage( &msg, &m_Host ); |
|
} |
|
} |
|
else |
|
{ |
|
// Find a team name that matches, and tell everyone our new team number |
|
char szTeamName[32]; |
|
uint id = g_ClientDLL->GetPresenceID( "PROPERTY_TEAM" ); |
|
|
|
for ( int iTeam = 0; iTeam < m_nTotalTeams; ++iTeam ) |
|
{ |
|
g_ClientDLL->GetPropertyDisplayString( id, iTeam, szTeamName, sizeof( szTeamName ) ); |
|
if ( !Q_stricmp( szTeamName, pTeamName ) ) |
|
{ |
|
bool bChanged = false; |
|
MM_ClientInfo info; |
|
ClientInfoToNetMessage( &info, &m_Local ); |
|
for ( int i = 0; i < m_Local.m_cPlayers; ++i ) |
|
{ |
|
if ( info.m_iTeam[i] != iTeam ) |
|
{ |
|
bChanged = true; |
|
} |
|
info.m_iTeam[i] = iTeam; |
|
} |
|
|
|
if ( !bChanged ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
ProcessClientInfo( &info ); |
|
} |
|
else |
|
{ |
|
SendMessage( &info, &m_Host ); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle various matchmaking checkpoint messages |
|
//----------------------------------------------------------------------------- |
|
bool CMatchmaking::ProcessCheckpoint( MM_Checkpoint *pMsg ) |
|
{ |
|
switch( pMsg->m_Checkpoint ) |
|
{ |
|
case MM_Checkpoint::CHECKPOINT_CHANGETEAM: |
|
if ( m_Session.IsHost() ) |
|
{ |
|
// if the countdown has started, deny |
|
if ( m_CurrentState == MMSTATE_ACCEPTING_CONNECTIONS ) |
|
{ |
|
netadr_t adr = pMsg->GetNetChannel()->GetRemoteAddress(); |
|
CClientInfo *pClient = FindClient( &adr ); |
|
if ( pClient ) |
|
{ |
|
SwitchToNextOpenTeam( pClient ); |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_PREGAME: |
|
|
|
if ( m_Session.IsArbitrated() && !m_Session.IsHost() ) |
|
{ |
|
m_Session.RegisterForArbitration(); |
|
} |
|
|
|
// Start the countdown timer to map load |
|
SwitchToState( MMSTATE_PREGAME ); |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_GAME_LOBBY: |
|
|
|
// returning to game lobby, pregame canceled |
|
// reset the countdown |
|
SessionNotification( SESSION_NOTIFY_COUNTDOWN, -1 ); |
|
if ( m_Session.IsHost() ) |
|
{ |
|
SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS ); |
|
} |
|
else |
|
{ |
|
SwitchToState( MMSTATE_SESSION_CONNECTED ); |
|
} |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_LOADING_COMPLETE: |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Mark this client as loaded |
|
netadr_t adr = pMsg->GetNetChannel()->GetRemoteAddress(); |
|
CClientInfo *pClient = FindClient( &adr ); |
|
if ( pClient ) |
|
{ |
|
pClient->m_bLoaded = true; |
|
|
|
if ( GameIsActive() ) |
|
{ |
|
// Send a reply and reset the netchannel timeout |
|
SendMessage( pMsg, pClient ); |
|
SetChannelTimeout( &pClient->m_adr, HEARTBEAT_TIMEOUT ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// The host is also loaded, so reset the netchannel timeout |
|
SetChannelTimeout( &m_Host.m_adr, HEARTBEAT_TIMEOUT ); |
|
} |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_CONNECT: |
|
|
|
// If we're already connected or in the game, don't connect again. |
|
if ( m_CurrentState == MMSTATE_CONNECTED_TO_SERVER || |
|
m_CurrentState == MMSTATE_INGAME ) |
|
{ |
|
break; |
|
} |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Send the message to everyone |
|
SendToRemoteClients( pMsg ); |
|
} |
|
else |
|
{ |
|
// The host is asking us to connect to the game server |
|
if ( m_CurrentState != MMSTATE_LOADING ) |
|
{ |
|
// Set the longer timeout for loading |
|
SetChannelTimeout( &m_Host.m_adr, HEARTBEAT_TIMEOUT_LOADING ); |
|
SwitchToState( MMSTATE_LOADING ); |
|
} |
|
} |
|
|
|
// Make sure we're not preventing full startup |
|
SetPreventFullServerStartup( false, "CHECKPOINT_CONNECT\n" ); |
|
|
|
if ( !IsServer() ) |
|
{ |
|
char cmd[MAX_PATH]; |
|
Q_snprintf( cmd, sizeof( cmd ), "connect %d.%d.%d.%d", m_Host.m_adr.ip[0], m_Host.m_adr.ip[1], m_Host.m_adr.ip[2], m_Host.m_adr.ip[3] ); |
|
Cbuf_AddText( cmd ); |
|
|
|
SessionNotification( SESSION_NOTIFY_CONNECTED_TOSERVER ); |
|
} |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_SESSION_DISCONNECT: |
|
|
|
PerformDisconnect(); |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_REPORT_STATS: |
|
|
|
// Start stats reporting |
|
g_ClientDLL->StartStatsReporting( m_Session.GetSessionHandle(), m_Session.IsArbitrated() ); |
|
m_Local.m_bReportedStats = true; |
|
|
|
m_fHeartbeatInterval = HEARTBEAT_INTERVAL_SHORT; |
|
|
|
SwitchToState( MMSTATE_REPORTING_STATS ); |
|
|
|
// Host needs to wait for clients to finish reporting |
|
if ( !m_Session.IsHost() ) |
|
{ |
|
EndStatsReporting(); |
|
} |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_REPORTING_COMPLETE: |
|
{ |
|
// Mark this client as finished reporting stats |
|
bool bFinished = false; |
|
netadr_t adr = pMsg->GetNetChannel()->GetRemoteAddress(); |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
CClientInfo *pClient = m_Remote[i]; |
|
if ( pClient->m_adr.CompareAdr( adr ) ) |
|
{ |
|
pClient->m_bReportedStats = true; |
|
} |
|
|
|
if ( !pClient->m_bReportedStats ) |
|
{ |
|
bFinished = false; |
|
} |
|
} |
|
|
|
if ( bFinished && m_Local.m_bReportedStats ) |
|
{ |
|
EndStatsReporting(); |
|
} |
|
} |
|
break; |
|
|
|
case MM_Checkpoint::CHECKPOINT_POSTGAME: |
|
|
|
g_pXboxSystem->SessionEnd( m_Session.GetSessionHandle(), false ); |
|
|
|
engineClient->ClientCmd( "disconnect" ); |
|
|
|
EngineVGui()->ActivateGameUI(); |
|
|
|
if ( m_Session.IsArbitrated() ) |
|
{ |
|
// Tell gameui to return to the main menu |
|
SessionNotification( SESSION_NOTIFY_ENDGAME_RANKED ); |
|
|
|
SwitchToState( MMSTATE_INITIAL ); |
|
} |
|
else |
|
{ |
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Make ourselves available to queries again |
|
m_HostData.gameState = GAMESTATE_INLOBBY; |
|
UpdateSessionReplyData( XNET_QOS_LISTEN_ENABLE|XNET_QOS_LISTEN_SET_DATA ); |
|
} |
|
|
|
// Tell gameui to activate the lobby |
|
SessionNotification( m_Session.IsHost() ? SESSION_NOTIFY_ENDGAME_HOST : SESSION_NOTIFY_ENDGAME_CLIENT ); |
|
|
|
// Fill the lobby with all of the clients |
|
if ( !m_Session.IsHost() ) |
|
{ |
|
SendPlayerInfoToLobby( &m_Host, m_nHostOwnerId ); |
|
SendPlayerInfoToLobby( &m_Local ); |
|
} |
|
else |
|
{ |
|
SendPlayerInfoToLobby( &m_Local, 0 ); |
|
} |
|
|
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
SendPlayerInfoToLobby( m_Remote[i] ); |
|
} |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS ); |
|
} |
|
else |
|
{ |
|
SwitchToState( MMSTATE_SESSION_CONNECTED ); |
|
} |
|
} |
|
break; |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stop any asynchronous operation that is currently running |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::CancelCurrentOperation() |
|
{ |
|
switch( m_CurrentState ) |
|
{ |
|
case MMSTATE_CREATING: |
|
m_Session.CancelCreateSession(); |
|
break; |
|
|
|
case MMSTATE_SEARCHING: |
|
CancelSearch(); |
|
break; |
|
|
|
case MMSTATE_WAITING_QOS: |
|
CancelQosLookup(); |
|
break; |
|
|
|
case MMSTATE_SESSION_CONNECTING: |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send player info to be displayed in the session lobby |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SendPlayerInfoToLobby( CClientInfo *pClient, int iHostIdx ) |
|
{ |
|
for ( int i = 0; i < pClient->m_cPlayers; ++i ) |
|
{ |
|
EngineVGui()->UpdatePlayerInfo( pClient->m_xuids[i], pClient->m_szGamertags[i], pClient->m_iTeam[i], pClient->m_cVoiceState[i], GetPlayersNeeded(), iHostIdx == i ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update the start game countdown timer |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::UpdatePregame() |
|
{ |
|
int elapsedTime = GetTime() - m_fCountdownStartTime; |
|
if ( elapsedTime > REGISTRATION_MAXWAITTIME ) |
|
{ |
|
// Check the registration timer. |
|
if ( m_Session.IsHost() && m_Session.IsArbitrated() && !m_Local.m_bRegistered ) |
|
{ |
|
// Time's up, register ourselves |
|
m_Local.m_bRegistered = true; |
|
m_Session.RegisterForArbitration(); |
|
} |
|
} |
|
|
|
// Check the countdown timer. When it's zero, start the game. |
|
if ( elapsedTime < STARTGAME_COUNTDOWN ) |
|
{ |
|
SessionNotification( SESSION_NOTIFY_COUNTDOWN, STARTGAME_COUNTDOWN - elapsedTime ); |
|
return; |
|
} |
|
|
|
// Send the zero count |
|
SessionNotification( SESSION_NOTIFY_COUNTDOWN, 0 ); |
|
|
|
// Set a longer timeout for loading |
|
if ( m_Session.IsHost() ) |
|
{ |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
SetChannelTimeout( &m_Remote[i]->m_adr, HEARTBEAT_TIMEOUT_LOADING ); |
|
} |
|
|
|
// Set commentary & cheats off |
|
ConVarRef commentary( "commentary" ); |
|
commentary.SetValue( false ); |
|
ConVarRef sv_cheats( "sv_cheats" ); |
|
sv_cheats.SetValue( 0 ); |
|
} |
|
else |
|
{ |
|
SetChannelTimeout( &m_Host.m_adr, HEARTBEAT_TIMEOUT_LOADING ); |
|
} |
|
|
|
g_pXboxSystem->SessionStart( m_Session.GetSessionHandle(), 0, false ); |
|
|
|
if ( !IsServer() ) |
|
{ |
|
SetPreventFullServerStartup( true, "SESSION_NOTIFY_COUNTDOWN == 0 and not the host\n" ); |
|
} |
|
|
|
SwitchToState( MMSTATE_LOADING ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Receive notifications from the session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SessionNotification( const SESSION_NOTIFY notification, const int param ) |
|
{ |
|
// Notify GameUI |
|
EngineVGui()->SessionNotification( notification, param ); |
|
|
|
switch( notification ) |
|
{ |
|
case SESSION_NOTIFY_CREATED_HOST: |
|
m_bEnteredLobby = true; |
|
OnHostSessionCreated(); |
|
break; |
|
|
|
case SESSION_NOTIFY_CREATED_CLIENT: |
|
Msg( "Client: CreateSession successful\n" ); |
|
|
|
// Session has been created according to the advertised specifications. |
|
// Now send a connection request to the session host. |
|
SwitchToState( MMSTATE_SESSION_CONNECTING ); |
|
break; |
|
|
|
case SESSION_NOFIFY_MODIFYING_SESSION: |
|
SwitchToState( MMSTATE_MODIFYING ); |
|
break; |
|
|
|
case SESSION_NOTIFY_MODIFYING_COMPLETED_HOST: |
|
SwitchToState( MMSTATE_ACCEPTING_CONNECTIONS ); |
|
break; |
|
|
|
case SESSION_NOTIFY_MODIFYING_COMPLETED_CLIENT: |
|
SwitchToState( MMSTATE_SESSION_CONNECTED ); |
|
break; |
|
|
|
case SESSION_NOTIFY_SEARCH_COMPLETED: |
|
// Waiting for the player to choose a session |
|
SwitchToState( MMSTATE_BROWSING ); |
|
break; |
|
|
|
case SESSION_NOTIFY_CONNECTED_TOSESSION: |
|
m_bEnteredLobby = true; |
|
SwitchToState( MMSTATE_SESSION_CONNECTED ); |
|
break; |
|
|
|
case SESSION_NOTIFY_CONNECTED_TOSERVER: |
|
SwitchToState( MMSTATE_CONNECTED_TO_SERVER ); |
|
break; |
|
|
|
case SESSION_NOTIFY_MIGRATION_COMPLETED: |
|
if ( !m_Session.IsHost() ) |
|
{ |
|
// Finished |
|
EndMigration(); |
|
} |
|
else |
|
{ |
|
// Get ready to send join requests too peers |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
m_Remote[i]->m_bMigrated = false; |
|
|
|
AddRemoteChannel( &m_Remote[i]->m_adr ); |
|
} |
|
|
|
m_nSendCount = 0; |
|
m_fSendTimer = 0; |
|
|
|
SwitchToState( MMSTATE_HOSTMIGRATE_WAITINGFORCLIENTS ); |
|
} |
|
break; |
|
|
|
case SESSION_NOTIFY_FAIL_MIGRATE: |
|
// X360TBD: How to handle this error? |
|
Warning( "Migrate Failed\n" ); |
|
break; |
|
|
|
case SESSION_NOTIFY_REGISTER_COMPLETED: |
|
if( !m_Session.IsHost() ) |
|
{ |
|
// Tell the host we're registered |
|
MM_RegisterResponse msg; |
|
SendMessage( &msg, &m_Host.m_adr ); |
|
} |
|
else |
|
{ |
|
// Process the results of registration |
|
ProcessRegistrationResults(); |
|
} |
|
break; |
|
|
|
case SESSION_NOTIFY_ENDGAME_HOST: |
|
case SESSION_NOTIFY_ENDGAME_CLIENT: |
|
m_fHeartbeatInterval = HEARTBEAT_INTERVAL_SHORT; |
|
break; |
|
|
|
case SESSION_NOTIFY_LOST_HOST: |
|
case SESSION_NOTIFY_LOST_SERVER: |
|
SwitchToState( MMSTATE_SESSION_DISCONNECTING ); |
|
break; |
|
|
|
case SESSION_NOTIFY_FAIL_REGISTER: |
|
case SESSION_NOTIFY_FAIL_CREATE: |
|
case SESSION_NOTIFY_FAIL_SEARCH: |
|
case SESSION_NOTIFY_CONNECT_SESSIONFULL: |
|
case SESSION_NOTIFY_CONNECT_NOTAVAILABLE: |
|
case SESSION_NOTIFY_CONNECT_FAILED: |
|
|
|
// Reset the session |
|
SwitchToState( MMSTATE_INITIAL ); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Switch between matchmaking states |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::SwitchToState( int newState ) |
|
{ |
|
// Clean up from the previous state |
|
switch( m_CurrentState ) |
|
{ |
|
case MMSTATE_INITIAL: |
|
break; |
|
|
|
case MMSTATE_BROWSING: |
|
// Clean up the search results |
|
ClearSearchResults(); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
// Initialize the next state |
|
switch( newState ) |
|
{ |
|
case MMSTATE_INITIAL: |
|
m_bCleanup = true; |
|
break; |
|
|
|
case MMSTATE_CREATING: |
|
case MMSTATE_SEARCHING: |
|
case MMSTATE_ACCEPTING_CONNECTIONS: |
|
break; |
|
|
|
case MMSTATE_MODIFYING: |
|
m_fSendTimer = GetTime(); |
|
break; |
|
|
|
case MMSTATE_WAITING_QOS: |
|
m_fWaitTimer = GetTime(); |
|
break; |
|
|
|
case MMSTATE_SESSION_CONNECTING: |
|
ConnectToHost(); |
|
break; |
|
|
|
case MMSTATE_PREGAME: |
|
m_fCountdownStartTime = GetTime(); |
|
break; |
|
|
|
case MMSTATE_LOADING: |
|
m_fHeartbeatInterval = HEARTBEAT_INTERVAL_LONG; |
|
break; |
|
|
|
case MMSTATE_SESSION_DISCONNECTING: |
|
m_fWaitTimer = GetTime(); |
|
break; |
|
|
|
case MMSTATE_REPORTING_STATS: |
|
m_fWaitTimer = GetTime(); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
m_CurrentState = newState; |
|
} |
|
|
|
void CMatchmaking::UpdateVoiceStatus( void ) |
|
{ |
|
#if defined( _X360 ) |
|
|
|
if ( m_flVoiceBlinkTime > GetTime() ) |
|
return; |
|
|
|
m_flVoiceBlinkTime = GetTime() + VOICE_ICON_BLINK_TIME; |
|
CClientInfo *pClient = &m_Local; |
|
|
|
bool bIsHost = m_Session.IsHost(); |
|
bool bShouldSendInfo = false; |
|
|
|
if ( pClient ) |
|
{ |
|
for ( int i = 0; i < pClient->m_cPlayers; ++i ) |
|
{ |
|
if ( pClient->m_xuids[i] == 0 ) |
|
continue; |
|
|
|
byte cOldVoiceState = pClient->m_cVoiceState[i]; |
|
|
|
if ( Audio_GetXVoice()->IsHeadsetPresent( pClient->m_iControllers[i] ) == false ) |
|
{ |
|
pClient->m_cVoiceState[i] = VOICE_STATUS_OFF; |
|
} |
|
else |
|
{ |
|
if ( Audio_GetXVoice()->IsPlayerTalking( pClient->m_xuids[i], true ) ) |
|
{ |
|
pClient->m_cVoiceState[i] = VOICE_STATUS_TALKING; |
|
} |
|
else |
|
{ |
|
pClient->m_cVoiceState[i] = VOICE_STATUS_IDLE; |
|
} |
|
} |
|
|
|
if ( cOldVoiceState != pClient->m_cVoiceState[i] ) |
|
{ |
|
bShouldSendInfo = true; |
|
} |
|
|
|
if ( bShouldSendInfo == true ) |
|
{ |
|
EngineVGui()->UpdatePlayerInfo( pClient->m_xuids[i], pClient->m_szGamertags[i], pClient->m_iTeam[i], pClient->m_cVoiceState[i], GetPlayersNeeded(), bIsHost ); |
|
} |
|
} |
|
|
|
|
|
if ( bShouldSendInfo ) |
|
{ |
|
MM_ClientInfo info; |
|
ClientInfoToNetMessage( &info, pClient ); |
|
|
|
if ( bIsHost == true ) |
|
{ |
|
// Tell all the clients |
|
ProcessClientInfo( &info ); |
|
} |
|
else |
|
{ |
|
// Tell all the clients |
|
SendMessage( &info, &m_Host ); |
|
} |
|
} |
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Update matchmaking and any active session |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::RunFrame() |
|
{ |
|
if ( !m_bInitialized ) |
|
{ |
|
RunFrameInvite(); |
|
return; |
|
} |
|
|
|
if ( NET_IsMultiplayer() ) |
|
{ |
|
NET_ProcessSocket( NS_MATCHMAKING, this ); |
|
|
|
if ( m_Session.IsSystemLink() ) |
|
{ |
|
NET_ProcessSocket( NS_SYSTEMLINK, this ); |
|
} |
|
} |
|
|
|
#if defined( _X360 ) |
|
if ( Audio_GetXVoice() != NULL ) |
|
{ |
|
if ( !GameIsActive() ) |
|
{ |
|
if ( Audio_GetXVoice()->VoiceUpdateData() == true ) |
|
{ |
|
CLC_VoiceData voice; |
|
Audio_GetXVoice()->GetVoiceData( &voice ); |
|
|
|
if ( m_Session.IsHost() ) |
|
{ |
|
// Send this message on to everyone else |
|
SendToRemoteClients( &voice, true ); |
|
} |
|
else |
|
{ |
|
// Send to the host |
|
SendMessage( &voice, &m_Host ); |
|
} |
|
|
|
Audio_GetXVoice()->VoiceResetLocalData(); |
|
} |
|
|
|
UpdateVoiceStatus(); |
|
} |
|
} |
|
#endif |
|
|
|
// Tell the session to run its update |
|
m_Session.RunFrame(); |
|
|
|
// Check state: |
|
switch( m_CurrentState ) |
|
{ |
|
case MMSTATE_CREATING: |
|
// Waiting for success or failure from CreateSession() |
|
// GameUI is displaying a "Creating Game" dialog. |
|
break; |
|
|
|
case MMSTATE_ACCEPTING_CONNECTIONS: |
|
// Host is sitting in the Lobby waiting for connection requests. Once the game |
|
// is full enough (player count >= min players) the host will be able to start the game. |
|
UpdateAcceptingConnections(); |
|
break; |
|
|
|
case MMSTATE_SEARCHING: |
|
UpdateSearch(); |
|
break; |
|
|
|
case MMSTATE_WAITING_QOS: |
|
UpdateQosLookup(); |
|
break; |
|
|
|
case MMSTATE_SESSION_CONNECTING: |
|
UpdateConnecting(); |
|
break; |
|
|
|
case MMSTATE_PREGAME: |
|
UpdatePregame(); |
|
break; |
|
|
|
case MMSTATE_MODIFYING: |
|
UpdateSessionModify(); |
|
break; |
|
|
|
case MMSTATE_HOSTMIGRATE_WAITINGFORCLIENTS: |
|
if ( GetTime() - m_fSendTimer > HOSTMIGRATION_RETRYINTERVAL ) |
|
{ |
|
if ( m_nSendCount > HOSTMIGRATION_MAXRETRIES ) |
|
{ |
|
EndMigration(); |
|
} |
|
else |
|
{ |
|
TellClientsToMigrate(); |
|
} |
|
} |
|
break; |
|
|
|
case MMSTATE_HOSTMIGRATE_WAITINGFORHOST: |
|
if ( GetTime() - m_fWaitTimer > HOSTMIGRATION_MAXWAITTIME ) |
|
{ |
|
// Give up on that host and try the next one in the list |
|
Msg( "Giving up on this host\n" ); |
|
ClientDropped( m_pNewHost ); |
|
|
|
StartHostMigration(); |
|
} |
|
break; |
|
|
|
case MMSTATE_SESSION_DISCONNECTING: |
|
// Wait for the host reply, or timeout |
|
if ( GetTime() - m_fWaitTimer > DISCONNECT_WAITTIME ) |
|
{ |
|
PerformDisconnect(); |
|
} |
|
break; |
|
|
|
case MMSTATE_REPORTING_STATS: |
|
if ( GetTime() - m_fWaitTimer > REPORTSTATS_WAITTIME ) |
|
{ |
|
EndStatsReporting(); |
|
} |
|
break; |
|
} |
|
|
|
CleanupMarkedChannels(); |
|
SendHeartbeat(); |
|
|
|
if ( m_bCleanup ) |
|
{ |
|
Cleanup(); |
|
} |
|
|
|
RunFrameInvite(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Let the invite system determine a good moment to start connecting to inviter's host |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::RunFrameInvite() |
|
{ |
|
if ( m_InviteState != INVITE_NONE ) |
|
{ |
|
JoinInviteSession( &m_InviteSessionInfo ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get Quality-of-Service with LIVE |
|
//----------------------------------------------------------------------------- |
|
MM_QOS_t CMatchmaking::GetQosWithLIVE() |
|
{ |
|
return MM_GetQos(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Debugging helpers |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::ShowSessionInfo() |
|
{ |
|
Msg( "[MM] Filled Slots:\n[MM] Public: %d of %d\n[MM] Private: %d of %d\n", |
|
m_Session.GetSessionSlots( SLOTS_FILLEDPUBLIC ), |
|
m_Session.GetSessionSlots( SLOTS_TOTALPUBLIC ), |
|
m_Session.GetSessionSlots( SLOTS_FILLEDPRIVATE ), |
|
m_Session.GetSessionSlots( SLOTS_TOTALPRIVATE ) ); |
|
|
|
Msg( "[MM] Current state: %d\n", m_CurrentState ); |
|
Msg( "[MM] Send timer: %f\n", GetTime() - m_fSendTimer ); |
|
Msg( "[MM] Wait timer: %f\n", GetTime() - m_fWaitTimer ); |
|
|
|
int total = 0; |
|
for ( int i = 0; i < m_nTotalTeams; ++i ) |
|
{ |
|
total += CountPlayersOnTeam( i ); |
|
} |
|
Msg( "[MM] Total players: %d\n", total ); |
|
|
|
EngineVGui()->SessionNotification( SESSION_NOTIFY_DUMPSTATS ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Debugging helpers |
|
//----------------------------------------------------------------------------- |
|
void CMatchmaking::TestSendMessage() |
|
{ |
|
for ( int i = 0; i < m_Remote.Count(); ++i ) |
|
{ |
|
AddRemoteChannel( &m_Remote[i]->m_adr ); |
|
} |
|
NET_StringCmd msg; |
|
SendToRemoteClients( &msg ); |
|
} |
|
|
|
bool CMatchmaking::PreventFullServerStartup() |
|
{ |
|
return m_bPreventFullServerStartup; |
|
} |
|
|
|
void CMatchmaking::SetPreventFullServerStartup( bool bState, char const *fmt, ... ) |
|
{ |
|
char desc[ 256 ]; |
|
va_list argptr; |
|
va_start( argptr, fmt ); |
|
Q_vsnprintf( desc, sizeof( desc ), fmt, argptr ); |
|
va_end( argptr ); |
|
|
|
DevMsg( 1, "Setting state from prevent %s to prevent %s: %s", |
|
m_bPreventFullServerStartup ? "yes" : "no", |
|
bState ? "yes" : "no", |
|
desc ); |
|
|
|
m_bPreventFullServerStartup = bState; |
|
} |
|
|
|
CON_COMMAND( mm_session_info, "Dump session information" ) |
|
{ |
|
if ( g_pMatchmaking ) |
|
{ |
|
g_pMatchmaking->ShowSessionInfo(); |
|
} |
|
} |
|
|
|
CON_COMMAND( mm_message, "Send a message to all remote clients" ) |
|
{ |
|
if ( g_pMatchmaking ) |
|
{ |
|
g_pMatchmaking->TestSendMessage(); |
|
} |
|
} |
|
|
|
CON_COMMAND( mm_stats, "" ) |
|
{ |
|
if ( g_pMatchmaking ) |
|
{ |
|
g_pMatchmaking->TestStats(); |
|
} |
|
}
|
|
|