summaryrefslogtreecommitdiff
path: root/engine/matchmakingshared.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/matchmakingshared.cpp')
-rw-r--r--engine/matchmakingshared.cpp2364
1 files changed, 2364 insertions, 0 deletions
diff --git a/engine/matchmakingshared.cpp b/engine/matchmakingshared.cpp
new file mode 100644
index 0000000..92eba94
--- /dev/null
+++ b/engine/matchmakingshared.cpp
@@ -0,0 +1,2364 @@
+//========= 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();
+ }
+}