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.
4034 lines
112 KiB
4034 lines
112 KiB
//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "mm_framework.h"
|
|
|
|
#include "fmtstr.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
ConVar mm_session_sys_delay_create( "mm_session_sys_delay_create", "0", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_sys_delay_create_host( "mm_session_sys_delay_create_host", "1.2", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_sys_timeout( "mm_session_sys_timeout", "3", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_sys_connect_timeout( "mm_session_sys_connect_timeout", "8", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_team_res_timeout( "mm_session_team_res_timeout", "30", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_voice_loading( "mm_session_voice_loading", "0", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_sys_ranking_timeout( "mm_session_sys_ranking_timeout", "12", FCVAR_DEVELOPMENTONLY );
|
|
ConVar mm_session_sys_pkey( "mm_session_sys_pkey", "", FCVAR_RELEASE );
|
|
ConVar mm_session_sys_kick_ban_duration( "mm_session_sys_kick_ban_duration", "180", FCVAR_RELEASE );
|
|
|
|
#define STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE 65536
|
|
|
|
#ifdef _X360
|
|
static CUtlVector< CSysSessionBase * > g_arrSysSessionsPending;
|
|
static CUtlVector< CSysSessionBase * > g_arrSysSessionsDelete;
|
|
|
|
void SysSession360_UpdatePending()
|
|
{
|
|
// Delete scheduled sessions
|
|
for ( int k = 0; k < g_arrSysSessionsDelete.Count(); ++ k )
|
|
{
|
|
CSysSessionBase *pSession = g_arrSysSessionsDelete[k];
|
|
DevMsg( "SysSession360_UpdatePending: destroying session %p\n", pSession );
|
|
|
|
pSession->Destroy();
|
|
g_arrSysSessionsPending.FindAndFastRemove( pSession );
|
|
}
|
|
g_arrSysSessionsDelete.Purge();
|
|
|
|
// Update pending sessions
|
|
for ( int k = 0; k < g_arrSysSessionsPending.Count(); ++ k )
|
|
{
|
|
CSysSessionBase *pSysSession = g_arrSysSessionsPending[k];
|
|
pSysSession->Update();
|
|
|
|
if ( k >= g_arrSysSessionsPending.Count() || pSysSession != g_arrSysSessionsPending[k] )
|
|
// If a pending session has removed itself, then proceed updating next frame
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SysSession360_RegisterPending( CSysSessionBase *pSession )
|
|
{
|
|
if ( g_arrSysSessionsPending.Find( pSession ) == g_arrSysSessionsPending.InvalidIndex() )
|
|
{
|
|
DevMsg( "SysSession360_RegisterPending: registered pending session %p\n", pSession );
|
|
g_arrSysSessionsPending.AddToTail( pSession );
|
|
}
|
|
else if ( g_arrSysSessionsDelete.Find( pSession ) == g_arrSysSessionsDelete.InvalidIndex() )
|
|
{
|
|
DevMsg( "SysSession360_RegisterPending: registered session %p for delete\n", pSession );
|
|
g_arrSysSessionsDelete.AddToTail( pSession );
|
|
}
|
|
else
|
|
Error( "SysSession360_RegisterPending for deleted session!" );
|
|
}
|
|
#endif
|
|
|
|
|
|
static bool SysSession_AllowCreate()
|
|
{
|
|
#ifdef _X360
|
|
// Cannot create new sessions while another session is pending
|
|
if ( g_arrSysSessionsPending.Count() )
|
|
return false;
|
|
#endif
|
|
|
|
static float s_fAllowCreateTime = 0.0f;
|
|
float flDelay = mm_session_sys_delay_create.GetFloat();
|
|
if ( flDelay <= 0 )
|
|
{
|
|
s_fAllowCreateTime = 0.0f;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( s_fAllowCreateTime > 0.0f )
|
|
{
|
|
if ( Plat_FloatTime() < s_fAllowCreateTime )
|
|
return false; // Delay time not elapsed yet
|
|
|
|
s_fAllowCreateTime = 0.0f;
|
|
return true; // Delay time has elapsed already
|
|
}
|
|
else
|
|
{
|
|
s_fAllowCreateTime = Plat_FloatTime() + flDelay;
|
|
return false; // Starting the delay time
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
CSysSessionBase::CSysSessionBase( KeyValues *pSettings ) :
|
|
#ifdef _X360
|
|
m_pAsyncOperation( NULL ),
|
|
m_pNetworkMgr( NULL ),
|
|
m_hLobbyMigrateCall( NULL ),
|
|
#elif !defined( NO_STEAM )
|
|
m_CallbackOnServersConnected( this, &CSysSessionBase::Steam_OnServersConnected ),
|
|
m_CallbackOnServersDisconnected( this, &CSysSessionBase::Steam_OnServersDisconnected ),
|
|
m_CallbackOnP2PSessionRequest( this, &CSysSessionBase::Steam_OnP2PSessionRequest ),
|
|
m_bVoiceUsingSessionP2P( false ),
|
|
#endif
|
|
m_Voice_flLastHeadsetStatusCheck( -1.0f ),
|
|
m_pSettings( pSettings ),
|
|
m_xuidMachineId( g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID() ),
|
|
m_result( RESULT_UNDEFINED )
|
|
{
|
|
}
|
|
|
|
|
|
CSysSessionBase::~CSysSessionBase()
|
|
{
|
|
;
|
|
}
|
|
|
|
bool CSysSessionBase::Update()
|
|
{
|
|
#ifdef _X360
|
|
if ( m_pNetworkMgr )
|
|
{
|
|
if ( m_pNetworkMgr->Update() != CX360NetworkMgr::UPDATE_SUCCESS )
|
|
return false; // the network manager destroyed or changed listener
|
|
}
|
|
#elif !defined( NO_STEAM )
|
|
if ( !IsServiceSession() )
|
|
{
|
|
// Process P2P network
|
|
uint32 uiSteamMsgSize = 0;
|
|
CUtlMemory< byte > utlMemory;
|
|
CSteamID idRemote;
|
|
while( steamapicontext->SteamNetworking()->IsP2PPacketAvailable( &uiSteamMsgSize, INetSupport::SP2PC_LOBBY ) )
|
|
{
|
|
utlMemory.EnsureCapacity( uiSteamMsgSize );
|
|
if ( steamapicontext->SteamNetworking()->ReadP2PPacket( utlMemory.Base(), uiSteamMsgSize, &uiSteamMsgSize, &idRemote, INetSupport::SP2PC_LOBBY ) )
|
|
UnpackAndReceiveMessage( utlMemory.Base(), uiSteamMsgSize, true, idRemote.ConvertToUint64() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Voice_UpdateLocalHeadsetsStatus();
|
|
Voice_CaptureAndTransmitLocalVoiceData();
|
|
|
|
#ifdef _X360
|
|
if ( m_pAsyncOperation )
|
|
{
|
|
m_pAsyncOperation->Update();
|
|
|
|
if ( m_pAsyncOperation->IsFinished() )
|
|
{
|
|
OnAsyncOperationFinished();
|
|
}
|
|
}
|
|
|
|
if ( UpdateMigrationCall() )
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSysSessionBase::IsServiceSession()
|
|
{
|
|
if ( !m_pSettings )
|
|
return true;
|
|
|
|
if ( char const *szNetFlag = m_pSettings->GetString( "system/netflag", NULL ) )
|
|
{
|
|
if ( !Q_stricmp( "teamlink", szNetFlag ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CSysSessionBase::OnSessionEvent( KeyValues *notify )
|
|
{
|
|
g_pMatchEventsSubscription->BroadcastEvent( notify );
|
|
}
|
|
|
|
void CSysSessionBase::SendEventsNotification( KeyValues *notify )
|
|
{
|
|
OnSessionEvent( notify );
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
void CSysSessionBase::ReleaseAsyncOperation()
|
|
{
|
|
if ( m_pAsyncOperation )
|
|
{
|
|
m_pAsyncOperation->Release();
|
|
m_pAsyncOperation = NULL;
|
|
}
|
|
}
|
|
|
|
INetSupport::NetworkSocket_t CSysSessionBase::GetX360NetSocket()
|
|
{
|
|
if ( char const *szNetFlag = m_pSettings->GetString( "system/netflag", NULL ) )
|
|
{
|
|
if ( !Q_stricmp( "teamlink", szNetFlag ) )
|
|
return INetSupport::NS_SOCK_TEAMLINK;
|
|
}
|
|
|
|
return INetSupport::NS_SOCK_LOBBY;
|
|
}
|
|
|
|
bool CSysSessionBase::ShouldAllowX360HostMigration()
|
|
{
|
|
if ( !m_lobby.m_hHandle )
|
|
return false;
|
|
|
|
// Check the session details
|
|
if ( dynamic_cast< CSysSessionHost * >( this ) )
|
|
{
|
|
return
|
|
m_pSettings->GetInt( "members/numMachines", 0 ) > 1 && // more than 1 machine in the session
|
|
!Q_stricmp( m_pSettings->GetString( "system/network" ), "LIVE" ); // migrate only on LIVE
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CSysSessionBase::UpdateMigrationCall()
|
|
{
|
|
if ( !m_hLobbyMigrateCall )
|
|
return false;
|
|
|
|
if ( !m_MigrateCallState.m_bFinished )
|
|
return false;
|
|
|
|
// The call was outstanding and is now finished
|
|
m_hLobbyMigrateCall = NULL;
|
|
|
|
if ( m_MigrateCallState.m_ret != ERROR_SUCCESS )
|
|
{
|
|
Assert( 0 );
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "migrate" );
|
|
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
return true;
|
|
}
|
|
|
|
// Prepare the notification
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
|
|
kvEvent->SetString( "migrate", "finished" );
|
|
|
|
if ( dynamic_cast< CSysSessionHost * >( this ) )
|
|
{
|
|
// We need to notify all our peers that we are now the new host and we have the new
|
|
// session details.
|
|
KeyValues *msg = new KeyValues( "SysSession::HostMigrated" );
|
|
KeyValues::AutoDelete autodelete( msg );
|
|
msg->SetUint64( "id", m_xuidMachineId );
|
|
|
|
char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ];
|
|
MMX360_SessionInfoToString( m_lobby.m_xiInfo, chSessionInfo );
|
|
msg->SetString( "sessioninfo", chSessionInfo );
|
|
|
|
// When the host migrated, we need to let the clients know which
|
|
// machines are still staying in the session
|
|
#ifdef _X360
|
|
if ( IsX360() && m_pNetworkMgr )
|
|
{
|
|
int numMachines = m_pSettings->GetInt( "members/numMachines" );
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
|
|
if ( !pMachine )
|
|
continue;
|
|
|
|
XUID idMachine = pMachine->GetUint64( "id" );
|
|
if ( idMachine &&
|
|
( idMachine == m_xuidMachineId ||
|
|
m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) ) )
|
|
{
|
|
msg->SetString( CFmtStr( "machines/%llx", idMachine ), "" );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
DevMsg( "CSysSessionHost - host migrated - %s\n", chSessionInfo );
|
|
SendMessage( msg );
|
|
|
|
#ifdef _X360
|
|
// Drop all machines that no longer have a network connection with us
|
|
// in case P2P interconnect failed earlier
|
|
if ( IsX360() && m_pNetworkMgr )
|
|
{
|
|
CUtlVector< XUID > arrXuidsNoP2P;
|
|
int numMachines = m_pSettings->GetInt( "members/numMachines" );
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
|
|
if ( !pMachine )
|
|
continue;
|
|
|
|
XUID idMachine = pMachine->GetUint64( "id" );
|
|
if ( idMachine &&
|
|
idMachine != m_xuidMachineId &&
|
|
!m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) )
|
|
arrXuidsNoP2P.AddToTail( idMachine );
|
|
}
|
|
for ( int k = 0; k < arrXuidsNoP2P.Count(); ++ k )
|
|
{
|
|
DevWarning( "UpdateMigrationCall - dropping machine %llx due to no P2P network connection!\n", arrXuidsNoP2P[k] );
|
|
OnPlayerLeave( arrXuidsNoP2P[k] );
|
|
}
|
|
|
|
//
|
|
// Update QOS reply data of the session
|
|
//
|
|
CUtlBuffer bufQosData;
|
|
bufQosData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufQosData );
|
|
g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID,
|
|
( const BYTE * ) bufQosData.Base(), bufQosData.TellMaxPut(),
|
|
0, XNET_QOS_LISTEN_SET_DATA | XNET_QOS_LISTEN_ENABLE );
|
|
}
|
|
#endif
|
|
|
|
// Send a notification
|
|
kvEvent->SetString( "state", "host" );
|
|
kvEvent->SetUint64( "xuid", m_xuidMachineId );
|
|
}
|
|
else
|
|
{
|
|
// Send a notification
|
|
DevMsg( "CSysSessionClient - client migration finished\n" );
|
|
|
|
kvEvent->SetString( "state", "client" );
|
|
kvEvent->SetUint64( "xuid", m_pSettings->GetUint64( "members/machine0/id", 0ull ) );
|
|
}
|
|
|
|
SendEventsNotification( kvEvent );
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
void CSysSessionBase::Destroy()
|
|
{
|
|
#ifdef _X360
|
|
// If the session is in an active gameplay state, first statistic reporting
|
|
// should be initiated on the session before it can be destroyed
|
|
if ( m_lobby.m_bXSessionStarted )
|
|
{
|
|
DevWarning( "CSysSessionBase::Destroy called on an active gameplay session, forcing XSessionEnd!\n" );
|
|
MMX360_LobbySetActiveGameplayState( m_lobby, false, NULL );
|
|
}
|
|
|
|
// If a migrate call was outstanding on the session, then disassociate the listener
|
|
if ( m_hLobbyMigrateCall )
|
|
{
|
|
MMX360_LobbyMigrateSetListener( m_hLobbyMigrateCall, NULL );
|
|
m_hLobbyMigrateCall = NULL;
|
|
}
|
|
|
|
bool bShouldAllowHostMigration = ShouldAllowX360HostMigration();
|
|
|
|
if ( KeyValues *msg = new KeyValues( "SysSession::Quit" ) )
|
|
{
|
|
KeyValues::AutoDelete autodelete( msg );
|
|
|
|
msg->SetUint64( "id", m_xuidMachineId );
|
|
SendMessage( msg );
|
|
}
|
|
|
|
if ( m_pNetworkMgr && !bShouldAllowHostMigration )
|
|
{
|
|
m_pNetworkMgr->Destroy();
|
|
m_pNetworkMgr = NULL;
|
|
}
|
|
|
|
ReleaseAsyncOperation();
|
|
|
|
Voice_ProcessTalkers( NULL, false );
|
|
|
|
if ( !bShouldAllowHostMigration )
|
|
{
|
|
if ( m_lobby.m_hHandle )
|
|
MMX360_LobbyDelete( m_lobby, &m_pAsyncOperation );
|
|
else
|
|
SysSession360_RegisterPending( this ); // registers first as if lobby delete has completed
|
|
|
|
m_pSettings = NULL;
|
|
SysSession360_RegisterPending( this );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Waiting for migration to finish
|
|
DevMsg( "CSysSessionBase::Destroy is waiting for migration to finish...\n" );
|
|
m_pSettings = NULL;
|
|
SysSession360_RegisterPending( this );
|
|
return;
|
|
}
|
|
|
|
#elif !defined( NO_STEAM )
|
|
Voice_ProcessTalkers( NULL, false );
|
|
|
|
if ( m_lobby.m_uiLobbyID )
|
|
{
|
|
if ( !IsServiceSession() )
|
|
{
|
|
for ( int k = 0, kNum = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); k < kNum; ++ k )
|
|
{
|
|
CSteamID idRemote = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k );
|
|
steamapicontext->SteamNetworking()->CloseP2PChannelWithUser( idRemote, INetSupport::SP2PC_LOBBY );
|
|
}
|
|
}
|
|
|
|
steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID );
|
|
}
|
|
m_lobby = CSteamLobbyObject();
|
|
#endif
|
|
|
|
delete this;
|
|
}
|
|
|
|
void CSysSessionBase::DebugPrint()
|
|
{
|
|
DevMsg( "CSysSessionBase\n" );
|
|
DevMsg( " machineid: %llx\n", m_xuidMachineId );
|
|
#ifdef _X360
|
|
DevMsg( " nonce: %llx\n", m_lobby.m_uiNonce );
|
|
DevMsg( " xhandle: %x\n", m_lobby.m_hHandle );
|
|
DevMsg( " xnkid: %llx\n", ( const uint64 & ) m_lobby.m_xiInfo.sessionID );
|
|
DevMsg( " xstarted: %d\n", m_lobby.m_bXSessionStarted );
|
|
|
|
XSESSION_LOCAL_DETAILS xld = {0};
|
|
DWORD dwSize = sizeof( xld );
|
|
DWORD ret = g_pMatchExtensions->GetIXOnline()->XSessionGetDetails( m_lobby.m_hHandle, &dwSize, &xld, NULL );
|
|
if ( ERROR_SUCCESS == ret )
|
|
{
|
|
DevMsg( " idx host: %d\n", xld.dwUserIndexHost );
|
|
DevMsg( " game type: %d\n", xld.dwGameType );
|
|
DevMsg( " game mode: %d\n", xld.dwGameMode );
|
|
DevMsg( " flags: 0x%X\n", xld.dwFlags );
|
|
DevMsg( " pub slots: %d/%d\n", xld.dwMaxPublicSlots - xld.dwAvailablePublicSlots, xld.dwMaxPublicSlots );
|
|
DevMsg( " pri slots: %d/%d\n", xld.dwMaxPrivateSlots - xld.dwMaxPrivateSlots, xld.dwMaxPrivateSlots );
|
|
DevMsg( " members: %d (%d)\n", xld.dwActualMemberCount, xld.dwReturnedMemberCount );
|
|
DevMsg( " xsesstate: %d\n", xld.eState );
|
|
DevMsg( " xnonce: %llx\n", xld.qwNonce );
|
|
DevMsg( " xnkid: %llx\n", ( const uint64 & ) xld.sessionInfo.sessionID );
|
|
DevMsg( " xnkid arb: %llx\n", ( const uint64 & ) xld.xnkidArbitration );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( " session sys details unavailable: code %d\n", ret );
|
|
}
|
|
|
|
if ( m_pNetworkMgr )
|
|
{
|
|
m_pNetworkMgr->DebugPrint();
|
|
}
|
|
else
|
|
{
|
|
DevMsg( " no network mgr\n" );
|
|
}
|
|
#elif !defined( NO_STEAM )
|
|
DevMsg( " lobby id: %llx\n", m_lobby.m_uiLobbyID );
|
|
DevMsg( " lbystate: %d\n", m_lobby.m_eLobbyState );
|
|
|
|
if ( m_lobby.m_uiLobbyID )
|
|
{
|
|
int numMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID );
|
|
DevMsg( " owner: %llx\n", steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() );
|
|
DevMsg( " members: %d/%s/%d\n", numMembers,
|
|
steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numSlots" ),
|
|
steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( m_lobby.m_uiLobbyID ) );
|
|
for ( int k = 0; k < numMembers; ++ k )
|
|
{
|
|
XUID xuid = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ).ConvertToUint64();
|
|
KeyValues *kvPlayer = SessionMembersFindPlayer( m_pSettings, xuid );
|
|
if ( kvPlayer )
|
|
{
|
|
DevMsg( " member%02d: %llx '%s'\n", k, xuid, kvPlayer->GetString( "name" ) );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( " member%02d: %llx <n/a>\n", k, xuid );
|
|
}
|
|
}
|
|
DevMsg( " ldata:net: %s\n", steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "system:network" ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionBase::Command( KeyValues *pCommand )
|
|
{
|
|
KeyValues *msg = new KeyValues( "SysSession::Command" );
|
|
KeyValues::AutoDelete autodelete( msg );
|
|
|
|
msg->AddSubKey( pCommand->MakeCopy() );
|
|
|
|
SendMessage( msg );
|
|
}
|
|
|
|
uint64 CSysSessionBase::GetReservationCookie()
|
|
{
|
|
if ( uint64 uiReservationCookieOverride = m_pSettings->GetUint64( "server/reservationid", 0ull ) )
|
|
return uiReservationCookieOverride;
|
|
|
|
return GetNonceCookie();
|
|
}
|
|
|
|
uint64 CSysSessionBase::GetNonceCookie()
|
|
{
|
|
#ifdef _X360
|
|
return m_lobby.m_uiNonce;
|
|
#elif !defined( NO_STEAM )
|
|
return m_lobby.GetSessionId();
|
|
#else
|
|
Assert( false ); // Not implemented for this platform
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
uint64 CSysSessionBase::GetSessionID()
|
|
{
|
|
return GetNonceCookie();
|
|
}
|
|
|
|
void CSysSessionBase::ReplyLanSearch( KeyValues *msg )
|
|
{
|
|
// Assemble a search reply message
|
|
KeyValues *reply = new KeyValues( "GameDetailsPlayer" );
|
|
KeyValues::AutoDelete autodelete( reply );
|
|
|
|
// Put information about our session
|
|
#ifdef _X360
|
|
char chSessionInfo[ XSESSION_INFO_STRING_LENGTH ] = {0};
|
|
MMX360_SessionInfoToString( m_lobby.m_xiInfo, chSessionInfo );
|
|
reply->SetString( "options/sessioninfo", chSessionInfo );
|
|
#elif !defined( NO_STEAM )
|
|
reply->SetUint64( "options/sessionid", m_lobby.m_uiLobbyID );
|
|
#endif
|
|
|
|
// Information about primary player
|
|
if ( IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() ) )
|
|
{
|
|
reply->SetString( "player/name", pPlayer->GetName() );
|
|
reply->SetUint64( "player/xuid", pPlayer->GetXUID() );
|
|
|
|
#ifdef _X360
|
|
XUSER_SIGNIN_INFO xsi;
|
|
if ( ERROR_SUCCESS == XUserGetSigninInfo( XBX_GetPrimaryUserId(), XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY, &xsi )
|
|
&& xsi.xuid )
|
|
{
|
|
reply->SetUint64( "player/xuidOnline", xsi.xuid );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Compose the binary encoding of game details
|
|
CUtlBuffer bufGameDetails;
|
|
bufGameDetails.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufGameDetails );
|
|
|
|
reply->SetPtr( "binary/ptr", bufGameDetails.Base() );
|
|
reply->SetInt( "binary/size", bufGameDetails.TellMaxPut() );
|
|
|
|
// Reply to sender
|
|
g_pConnectionlessLanMgr->SendPacket( reply,
|
|
( !IsX360() && msg ) ? msg->GetString( "from", NULL ) : NULL );
|
|
}
|
|
|
|
void CSysSessionBase::SendMessage( KeyValues *msg )
|
|
{
|
|
#ifdef _X360
|
|
if ( m_pNetworkMgr )
|
|
m_pNetworkMgr->ConnectionPeerSendMessage( msg );
|
|
|
|
// Do not receive my own quit message
|
|
bool bCanReceive = !!Q_stricmp( "SysSession::Quit", msg->GetName() );
|
|
|
|
if ( bCanReceive )
|
|
ReceiveMessage( msg, true );
|
|
#elif !defined( NO_STEAM )
|
|
|
|
CUtlBuffer buf;
|
|
buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
|
|
|
|
msg->WriteAsBinary( buf );
|
|
|
|
// Special case when encoding binary data
|
|
KeyValues *kvPtr = msg->FindKey( "binary/ptr" );
|
|
KeyValues *kvSize = msg->FindKey( "binary/size" );
|
|
if ( kvPtr && kvSize )
|
|
{
|
|
void *pvData = kvPtr->GetPtr();
|
|
int nSize = kvSize->GetInt();
|
|
|
|
if ( pvData && nSize )
|
|
{
|
|
buf.Put( pvData, nSize );
|
|
}
|
|
}
|
|
|
|
if ( char const *szP2P = msg->GetString( "p2p", NULL ) )
|
|
{
|
|
Assert( !IsServiceSession() );
|
|
|
|
// Determine P2P type
|
|
EP2PSend eSendType = k_EP2PSendUnreliableNoDelay; // "nodelay"
|
|
if ( !Q_stricmp( szP2P, "reliable" ) )
|
|
eSendType = k_EP2PSendReliable;
|
|
else if ( !Q_stricmp( szP2P, "buffer" ) )
|
|
eSendType = k_EP2PSendReliableWithBuffering;
|
|
else if ( !Q_stricmp( szP2P, "unreliable" ) )
|
|
eSendType = k_EP2PSendUnreliable;
|
|
|
|
// Transmit P2P message
|
|
// for ( int k = 0, kNum = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID ); k < kNum; ++ k )
|
|
// {
|
|
// CSteamID idRemote = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k );
|
|
// if ( idRemote.ConvertToUint64() != m_xuidMachineId )
|
|
// steamapicontext->SteamNetworking()->SendP2PPacket( idRemote, buf.Base(), buf.TellMaxPut(), eSendType, INetSupport::SP2PC_LOBBY );
|
|
// }
|
|
for ( int k = 0, numMachines = m_pSettings->GetInt( "members/numMachines" ); k < numMachines; ++ k )
|
|
{
|
|
for ( int ic = 0, numPlayers = m_pSettings->GetInt( CFmtStr( "members/machine%d/numPlayers", k ) ); ic < numPlayers; ++ ic )
|
|
{
|
|
XUID xuidPlayer = m_pSettings->GetUint64( CFmtStr( "members/machine%d/player%d/xuid", k, ic ) );
|
|
if ( xuidPlayer && xuidPlayer != m_xuidMachineId )
|
|
{
|
|
steamapicontext->SteamNetworking()->SendP2PPacket( xuidPlayer, buf.Base(), buf.TellMaxPut(), eSendType, INetSupport::SP2PC_LOBBY );
|
|
m_bVoiceUsingSessionP2P = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Receive on our own side
|
|
ReceiveMessage( msg, true, m_xuidMachineId );
|
|
}
|
|
else if ( m_lobby.m_uiLobbyID )
|
|
{
|
|
steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_lobby.m_uiLobbyID, buf.Base(), buf.TellMaxPut() );
|
|
}
|
|
else
|
|
{
|
|
ReceiveMessage( msg, true, m_xuidMachineId );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionBase::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
|
|
{
|
|
char const *szMsg = msg->GetName();
|
|
|
|
if ( !Q_stricmp( "SysSession::Quit", szMsg ) )
|
|
{
|
|
XUID xuidMachine = msg->GetUint64( "id" );
|
|
Assert( xuidMachine == xuidSrc );
|
|
// assuming that xuidMachine and xuid of primary user are same
|
|
OnPlayerLeave( xuidSrc );
|
|
}
|
|
else if ( !Q_stricmp( "SysSession::Command", szMsg ) )
|
|
{
|
|
KeyValues *pCommand = msg->GetFirstTrueSubKey();
|
|
if ( !pCommand )
|
|
return;
|
|
|
|
char const *szRun = pCommand->GetString( "run", "" );
|
|
bool bRun = false;
|
|
|
|
if ( !Q_stricmp( szRun, "all" ) )
|
|
bRun = true;
|
|
else if ( !Q_stricmp( szRun, "host" ) && dynamic_cast< CSysSessionHost * >( this ) )
|
|
bRun = true;
|
|
else if ( !Q_stricmp( szRun, "clients" ) && dynamic_cast< CSysSessionClient * >( this ) )
|
|
bRun = true;
|
|
else if ( !Q_stricmp( szRun, "xuid" ) && m_xuidMachineId == pCommand->GetUint64( "runxuid" ) )
|
|
bRun = true;
|
|
|
|
if ( bRun )
|
|
{
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionCommand" );
|
|
|
|
msg->RemoveSubKey( pCommand );
|
|
kv->AddSubKey( pCommand );
|
|
|
|
// We always set these field regardless of what was in command to indicate that it arrived from a remote machine
|
|
bool bHostSrcXuid = ( xuidSrc == GetHostXuid() );
|
|
pCommand->SetBool( "_remote_host", bHostSrcXuid );
|
|
pCommand->SetUint64( "_remote_xuidsrc", xuidSrc );
|
|
|
|
kv->SetBool( "host", bHostSrcXuid );
|
|
kv->SetUint64( "xuidsrc", xuidSrc );
|
|
kv->SetPtr( "syssession", this );
|
|
|
|
OnSessionEvent( kv );
|
|
}
|
|
}
|
|
else if ( !Q_stricmp( "SysSession::Voice", szMsg ) )
|
|
{
|
|
Voice_Playback( msg, xuidSrc );
|
|
}
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
void CSysSessionBase::OnX360NetPacket( KeyValues *msg )
|
|
{
|
|
ReceiveMessage( msg, true );
|
|
}
|
|
|
|
void CSysSessionBase::OnX360NetDisconnected( XUID xuidRemote )
|
|
{
|
|
OnPlayerLeave( xuidRemote );
|
|
}
|
|
|
|
void CSysSessionBase::OnX360AllSessionMembersJoinLeave( KeyValues *kv )
|
|
{
|
|
char const *szLock = kv->GetString( "system/lock", NULL );
|
|
char const *szAccess = kv->GetString( "system/access", NULL );
|
|
if ( szAccess || ( szLock && Q_stricmp( szLock, "endgame" ) && !IsX360() ) )
|
|
{
|
|
if ( m_lobby.m_bXSessionStarted )
|
|
{
|
|
DevWarning( "CSysSessionBase::OnX360AllSessionMembersJoinLeave cannot be called on an active gameplay session!\n" );
|
|
Assert( !m_lobby.m_bXSessionStarted );
|
|
}
|
|
|
|
MMX360_LobbyLeaveMembers( m_pSettings, m_lobby );
|
|
{
|
|
CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( m_pSettings, !!dynamic_cast< CSysSessionHost * >( this ) );
|
|
|
|
g_pMatchExtensions->GetIXOnline()->XSessionModify( m_lobby.m_hHandle, fl.m_dwFlags, fl.m_numPublicSlots, fl.m_numPrivateSlots, MMX360_NewOverlappedDormant() );
|
|
}
|
|
MMX360_LobbyJoinMembers( m_pSettings, m_lobby );
|
|
}
|
|
}
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
void CSysSessionBase::UnpackAndReceiveMessage( const void *pvBuffer, int numBytes, bool bValidatedLobbyMember, XUID xuidSrc )
|
|
{
|
|
if ( numBytes <= 0 )
|
|
return;
|
|
|
|
CUtlBuffer buf( pvBuffer, numBytes, CUtlBuffer::READ_ONLY );
|
|
buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
if ( buf.GetInt() != g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() )
|
|
return;
|
|
|
|
KeyValues *msg = new KeyValues( "" );
|
|
KeyValues::AutoDelete autodelete( msg );
|
|
if ( !msg->ReadAsBinary( buf ) )
|
|
return;
|
|
|
|
// Special case when decoding binary data
|
|
static byte chBuffer2[ STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ];
|
|
KeyValues *kvPtr = msg->FindKey( "binary/ptr" );
|
|
KeyValues *kvSize = msg->FindKey( "binary/size" );
|
|
if ( kvPtr && kvSize )
|
|
{
|
|
void *pvData = kvPtr->GetPtr();
|
|
int nSize = kvSize->GetInt();
|
|
|
|
if ( nSize < 0 || nSize >= STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE )
|
|
return;
|
|
|
|
if ( pvData && nSize )
|
|
{
|
|
if ( !buf.Get( chBuffer2, nSize ) )
|
|
return;
|
|
|
|
kvPtr->SetPtr( NULL, chBuffer2 );
|
|
}
|
|
}
|
|
|
|
ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
|
|
}
|
|
|
|
// We should keep this global since client DLL writes a value here
|
|
volatile uint32 *g_hRankingSetupCallHandle = 0;
|
|
|
|
void CSysSessionBase::SetupSteamRankingConfiguration()
|
|
{
|
|
KeyValues *kvNotification = new KeyValues( "SetupSteamRankingConfiguration" );
|
|
kvNotification->SetPtr( "settingsptr", m_pSettings );
|
|
kvNotification->SetPtr( "callhandleptr", ( void * ) &g_hRankingSetupCallHandle );
|
|
|
|
SendEventsNotification( kvNotification );
|
|
}
|
|
|
|
bool CSysSessionBase::IsSteamRankingConfigured() const
|
|
{
|
|
return !g_hRankingSetupCallHandle || !*g_hRankingSetupCallHandle;
|
|
}
|
|
|
|
void CSysSessionBase::Steam_OnLobbyChatMsg( LobbyChatMsg_t *pLobbyChatMsg )
|
|
{
|
|
if ( pLobbyChatMsg->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
static byte chBuffer[ STEAM_LOBBY_CHAT_MSG_BUFFER_SIZE ];
|
|
|
|
CSteamID steamIDSender;
|
|
EChatEntryType ecet;
|
|
|
|
int numBytes = steamapicontext->SteamMatchmaking()->GetLobbyChatEntry(
|
|
m_lobby.m_uiLobbyID, pLobbyChatMsg->m_iChatID,
|
|
&steamIDSender,
|
|
chBuffer, sizeof( chBuffer ),
|
|
&ecet );
|
|
|
|
// Is this a validated lobby member?
|
|
bool bValidatedLobbyMember = ( SessionMembersFindPlayer( m_pSettings, steamIDSender.ConvertToUint64() ) != NULL );
|
|
|
|
UnpackAndReceiveMessage( chBuffer, numBytes, bValidatedLobbyMember, steamIDSender.ConvertToUint64() );
|
|
}
|
|
|
|
void CSysSessionBase::Steam_OnLobbyChatUpdate( LobbyChatUpdate_t *pLobbyChatUpdate )
|
|
{
|
|
if ( pLobbyChatUpdate->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
if ( BChatMemberStateChangeRemoved( pLobbyChatUpdate->m_rgfChatMemberStateChange ) )
|
|
{
|
|
XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
|
|
if ( pLobbyChatUpdate->m_ulSteamIDUserChanged == xuidLocal )
|
|
{
|
|
if ( pLobbyChatUpdate->m_ulSteamIDMakingChange != xuidLocal )
|
|
{
|
|
// Prepare the update notification
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "kicked" );
|
|
|
|
SendEventsNotification( kv );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OnPlayerLeave( pLobbyChatUpdate->m_ulSteamIDUserChanged );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::Steam_OnP2PSessionRequest( P2PSessionRequest_t *pParam )
|
|
{
|
|
uint64 idRemote = pParam->m_steamIDRemote.ConvertToUint64();
|
|
if ( m_lobby.m_uiLobbyID && g_pMatchExtensions->GetIVEngineClient() &&
|
|
( !g_pMatchExtensions->GetIVEngineClient()->IsConnected() || g_pMatchExtensions->GetIVEngineClient()->IsClientLocalToActiveServer() ) &&
|
|
SessionMembersFindPlayer( m_pSettings, idRemote ) &&
|
|
( !IsPS3() || ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DEFAULT ) ) )
|
|
{
|
|
// We are in the lobby together, accept P2P session request
|
|
steamapicontext->SteamNetworking()->AcceptP2PSessionWithUser( idRemote );
|
|
m_bVoiceUsingSessionP2P = true;
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::Steam_OnServersConnected( SteamServersConnected_t *pParam )
|
|
{
|
|
}
|
|
|
|
void CSysSessionBase::Steam_OnServersDisconnected( SteamServersDisconnected_t *pParam )
|
|
{
|
|
// In case we are not in active gameplay we should just drop
|
|
// back to main menu while Steam is disconnected
|
|
if ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DEFAULT )
|
|
{
|
|
// Prepare the update notification
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "SteamServersDisconnected" );
|
|
|
|
SendEventsNotification( kv );
|
|
return;
|
|
}
|
|
|
|
// If we already got disconnected once prevent re-entry
|
|
if ( m_lobby.m_eLobbyState == CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM )
|
|
return;
|
|
m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM;
|
|
|
|
// Otherwise we should manually leave the lobby
|
|
steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID );
|
|
m_lobby.m_uiLobbyID = 0ull;
|
|
|
|
// set the session lock
|
|
m_pSettings->SetString( "system/lock", "SteamServersDisconnected" );
|
|
|
|
// Check if we can remove all remote players from the session
|
|
if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) )
|
|
{
|
|
DevMsg( "CSysSessionBase::Steam_OnServersDisconnected keeping players in noleave mode.\n" );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Remove all the remote players from the session
|
|
//
|
|
XUID xuidLocal = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
|
|
KeyValues *pMembers = m_pSettings->FindKey( "members" );
|
|
Assert( pMembers );
|
|
if ( !pMembers )
|
|
return;
|
|
|
|
int numMachines = pMembers->GetInt( "numMachines", 0 );
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *kvMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
|
|
if ( !kvMachine )
|
|
continue;
|
|
XUID idMachine = kvMachine->GetUint64( "id" );
|
|
if ( idMachine == xuidLocal )
|
|
{
|
|
kvMachine->SetName( "machine0" );
|
|
|
|
pMembers->SetInt( "numPlayers", kvMachine->GetInt( "numPlayers", 1 ) );
|
|
pMembers->SetInt( "numSlots", kvMachine->GetInt( "numPlayers", 1 ) );
|
|
}
|
|
else
|
|
{
|
|
pMembers->RemoveSubKey( kvMachine );
|
|
kvMachine->deleteThis();
|
|
}
|
|
}
|
|
pMembers->SetInt( "numMachines", 1 );
|
|
}
|
|
|
|
char const * CSysSessionBase::LobbyEnterErrorAsString( LobbyEnter_t *pLobbyEnter )
|
|
{
|
|
switch ( pLobbyEnter->m_EChatRoomEnterResponse )
|
|
{
|
|
case k_EChatRoomEnterResponseFull:
|
|
return "full";
|
|
|
|
case k_EChatRoomEnterResponseBanned:
|
|
case k_EChatRoomEnterResponseNotAllowed:
|
|
return "notwanted";
|
|
case k_EChatRoomEnterResponseMemberBlockedYou:
|
|
return "blockedyou";
|
|
case k_EChatRoomEnterResponseYouBlockedMember:
|
|
return "youblocked";
|
|
|
|
case k_EChatRoomEnterResponseDoesntExist:
|
|
return "doesntexist";
|
|
|
|
case k_EChatRoomEnterResponseRatelimitExceeded:
|
|
return "ratelimit";
|
|
|
|
default:
|
|
return "create";
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::LobbySetDataFromKeyValues( char const *szPath, KeyValues *key, bool bRecurse )
|
|
{
|
|
if ( !key || !szPath )
|
|
return;
|
|
|
|
char chKey[ 256 ];
|
|
char chValue[ 256 ];
|
|
|
|
if ( key->GetDataType() != KeyValues::TYPE_NONE )
|
|
{
|
|
PrintValue( key, chValue, ARRAYSIZE( chValue ) );
|
|
DevMsg( "LobbySetData: '%s' = '%s'\n", szPath, chValue );
|
|
steamapicontext->SteamMatchmaking()->SetLobbyData( m_lobby.m_uiLobbyID, szPath, chValue );
|
|
}
|
|
else for ( KeyValues *sub = key->GetFirstSubKey(); sub; sub = sub->GetNextKey() )
|
|
{
|
|
if ( !bRecurse && sub->GetDataType() == KeyValues::TYPE_NONE )
|
|
continue;
|
|
|
|
Q_snprintf( chKey, ARRAYSIZE( chKey ), "%s:%s", szPath, sub->GetName() );
|
|
if ( Q_stricmp( chKey, "System:dependentlobby" ) == 0 )
|
|
{
|
|
// set magic dependent lobby something
|
|
steamapicontext->SteamMatchmaking()->SetLinkedLobby( m_lobby.m_uiLobbyID, sub->GetUint64() );
|
|
}
|
|
else
|
|
LobbySetDataFromKeyValues( chKey, sub, bRecurse );
|
|
}
|
|
|
|
// Expose lobby members too because Steam doesn't do it for us (lobby owner is first)
|
|
if ( !V_stricmp( szPath, "members" ) )
|
|
{
|
|
CUtlVector< AccountID_t > arrAccounts;
|
|
int numMachines = key->GetInt( "numMachines", 0 );
|
|
arrAccounts.EnsureCapacity( numMachines + 1 );
|
|
arrAccounts.AddToTail( CSteamID( GetHostXuid() ).GetAccountID() );
|
|
for ( int k = 0; k < numMachines; ++k )
|
|
{
|
|
KeyValues *kvMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
|
|
if ( kvMachine )
|
|
{
|
|
uint64 ullMachineID = kvMachine->GetUint64( "id" );
|
|
if ( AccountID_t unAccountID = CSteamID( ullMachineID ).GetAccountID() )
|
|
{
|
|
if ( unAccountID != arrAccounts.Head() )
|
|
arrAccounts.AddToTail( unAccountID );
|
|
}
|
|
}
|
|
}
|
|
|
|
// We are going to write out these bytes as "varints" encoding,
|
|
// which also ensures that they can fit in lobby metadata as no
|
|
// single byte will be 0x00
|
|
const int numBytesStackAlloc = ( 1 + arrAccounts.Count() ) * 5; // 5 bytes max per varint
|
|
uint8 * const packedData = ( uint8 * ) stackalloc( numBytesStackAlloc );
|
|
uint8 *pWrite = packedData;
|
|
FOR_EACH_VEC( arrAccounts, k )
|
|
{
|
|
uint32 data = arrAccounts[k];
|
|
while ( data > 0x7F )
|
|
{
|
|
*( pWrite ++ ) = uint8( ( data & 0x7F ) | 0x80 );
|
|
data >>= 7;
|
|
}
|
|
Assert( data & 0x7F );
|
|
*( pWrite ++ ) = uint8( data & 0x7F );
|
|
}
|
|
*( pWrite ) = 0; // null-terminator for the "string"
|
|
steamapicontext->SteamMatchmaking()->SetLobbyData( m_lobby.m_uiLobbyID, "uids", ( char * ) packedData );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void CSysSessionBase::Voice_ProcessTalkers( KeyValues *pMachine, bool bAdd )
|
|
{
|
|
if ( IsServiceSession() )
|
|
return;
|
|
|
|
if ( !pMachine ) // Process all members as talkers
|
|
{
|
|
int numMachines = m_pSettings->GetInt( "members/numMachines", 0 );
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *kvMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
|
|
if ( kvMachine )
|
|
Voice_ProcessTalkers( kvMachine, bAdd );
|
|
}
|
|
|
|
if ( bAdd && m_Voice_flLastHeadsetStatusCheck < 0 )
|
|
m_Voice_flLastHeadsetStatusCheck = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
XUID xuidMachine = pMachine->GetUint64( "id", 0ull );
|
|
if ( !xuidMachine )
|
|
return;
|
|
|
|
int numPlayers = pMachine->GetInt( "numPlayers", 0 );
|
|
uint64 uiMachineFlags = pMachine->GetUint64( "flags" );
|
|
for ( int k = 0; k < numPlayers; ++ k )
|
|
{
|
|
XUID xuid = pMachine->GetUint64( CFmtStr( "player%d/xuid", k ), 0ull );
|
|
if ( !xuid )
|
|
continue;
|
|
|
|
int iCtrlr = -1;
|
|
if ( xuidMachine == m_xuidMachineId )
|
|
{
|
|
iCtrlr = XBX_GetUserId( k );
|
|
xuid = 0ull; // for local users we use only controller index
|
|
}
|
|
|
|
if ( IEngineVoice *pIEngineVoice = g_pMatchExtensions->GetIEngineVoice() )
|
|
{
|
|
if ( bAdd )
|
|
pIEngineVoice->AddPlayerToVoiceList( xuid, iCtrlr, (uiMachineFlags & MACHINE_PLATFORM_PS3) ? ENGINE_VOICE_FLAG_PS3 : 0 );
|
|
else
|
|
{
|
|
pIEngineVoice->RemovePlayerFromVoiceList( xuid, iCtrlr );
|
|
#if !defined( NO_STEAM )
|
|
// When removing from voice list tear down P2P session
|
|
steamapicontext->SteamNetworking()->CloseP2PSessionWithUser( xuid );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::Voice_CaptureAndTransmitLocalVoiceData()
|
|
{
|
|
if ( IsServiceSession() )
|
|
return;
|
|
|
|
IEngineVoice *v = g_pMatchExtensions->GetIEngineVoice();
|
|
if ( !v )
|
|
return;
|
|
|
|
if ( g_pMatchFramework->GetMatchTitle()->GetTitleSettingsFlags() & MATCHTITLE_VOICE_INGAME )
|
|
{
|
|
#ifdef _X360
|
|
if ( m_lobby.m_bXSessionStarted )
|
|
return;
|
|
#elif !defined( NO_STEAM )
|
|
if ( m_lobby.m_eLobbyState != m_lobby.STATE_DEFAULT )
|
|
{
|
|
if ( IsPS3() && m_bVoiceUsingSessionP2P )
|
|
{
|
|
m_bVoiceUsingSessionP2P = false;
|
|
// As soon as we start loading into a game we should shutdown all P2P communication
|
|
for ( int k = 0, numMachines = m_pSettings->GetInt( "members/numMachines" ); k < numMachines; ++ k )
|
|
{
|
|
for ( int ic = 0, numPlayers = m_pSettings->GetInt( CFmtStr( "members/machine%d/numPlayers", k ) ); ic < numPlayers; ++ ic )
|
|
{
|
|
XUID xuidPlayer = m_pSettings->GetUint64( CFmtStr( "members/machine%d/player%d/xuid", k, ic ) );
|
|
if ( xuidPlayer && xuidPlayer != m_xuidMachineId )
|
|
{
|
|
steamapicontext->SteamNetworking()->CloseP2PSessionWithUser( xuidPlayer );
|
|
DevMsg( "PS3 Session has shut down P2P session with %llx!\n", xuidPlayer );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for ( DWORD k = 0; k < XBX_GetNumGameUsers(); ++ k )
|
|
{
|
|
#ifdef _GAMECONSOLE
|
|
int iCtrlr = XBX_GetUserId( k );
|
|
#else
|
|
int iCtrlr = XBX_GetPrimaryUserId();
|
|
#endif
|
|
|
|
if ( v->VoiceUpdateData( iCtrlr ) )
|
|
{
|
|
// Capture the voice data buffers
|
|
const byte *pbVoiceData = NULL;
|
|
unsigned int numBytes = 0;
|
|
v->GetVoiceData( iCtrlr, &pbVoiceData, &numBytes );
|
|
|
|
if ( mm_session_voice_loading.GetBool() || !(
|
|
g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() ||
|
|
g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) )
|
|
{
|
|
// Package it up as a message
|
|
KeyValues *msg = new KeyValues( "SysSession::Voice" );
|
|
KeyValues::AutoDelete autodelete_msg( msg );
|
|
|
|
msg->SetString( "p2p", "nodelay" ); // marks the message as preferring p2p transfer
|
|
msg->SetUint64( "xuid", g_pPlayerManager->GetLocalPlayer( iCtrlr )->GetXUID() );
|
|
|
|
unsigned int numBytesRemaining = numBytes, numBytesOffset = 0;
|
|
while ( numBytesRemaining > 0 )
|
|
{
|
|
numBytes = MIN( 1024, numBytesRemaining );
|
|
msg->SetPtr( "binary/ptr", numBytesOffset + const_cast< byte * >( pbVoiceData ) );
|
|
msg->SetInt( "binary/size", numBytes );
|
|
|
|
numBytesRemaining -= numBytes;
|
|
numBytesOffset += numBytes;
|
|
|
|
|
|
// Deliver the message to peers
|
|
SendMessage( msg );
|
|
}
|
|
}
|
|
|
|
// Reset voice buffers
|
|
v->VoiceResetLocalData( iCtrlr );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::Voice_Playback( KeyValues *msg, XUID xuidSrc )
|
|
{
|
|
XUID xuid = msg->GetUint64( "xuid", 0ull );
|
|
Assert( xuid == xuidSrc );
|
|
if ( xuid != xuidSrc )
|
|
return;
|
|
|
|
const byte *pbVoiceData = ( const byte * ) msg->GetPtr( "binary/ptr" );
|
|
unsigned int numBytes = msg->GetInt( "binary/size" );
|
|
|
|
if ( !pbVoiceData || !numBytes )
|
|
// We cannot playback the data or muting the player
|
|
return;
|
|
|
|
if ( mm_session_voice_loading.GetBool() || !(
|
|
g_pMatchExtensions->GetIVEngineClient()->IsDrawingLoadingImage() ||
|
|
g_pMatchExtensions->GetIVEngineClient()->IsTransitioningToLoad() ) )
|
|
{
|
|
g_pMatchExtensions->GetIEngineVoice()->PlayIncomingVoiceData( xuid, pbVoiceData, numBytes );
|
|
|
|
if ( g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( xuid ) )
|
|
{
|
|
if ( KeyValues *notify = new KeyValues( "OnPlayerActivity", "act", "voice" ) )
|
|
{
|
|
notify->SetUint64( "xuid", xuid );
|
|
OnSessionEvent( notify );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::Voice_UpdateLocalHeadsetsStatus()
|
|
{
|
|
if ( IsServiceSession() )
|
|
return;
|
|
|
|
if ( m_Voice_flLastHeadsetStatusCheck < 0 )
|
|
return;
|
|
|
|
// Not too frequently check for the headset status changes
|
|
if ( Plat_FloatTime() - m_Voice_flLastHeadsetStatusCheck < 1.0f )
|
|
return;
|
|
m_Voice_flLastHeadsetStatusCheck = Plat_FloatTime();
|
|
|
|
// Find the local machine
|
|
KeyValues *pMachine = NULL;
|
|
SessionMembersFindPlayer( m_pSettings, m_xuidMachineId, &pMachine );
|
|
if ( !pMachine )
|
|
return;
|
|
|
|
// Whether current status is different from session settings
|
|
for ( uint k = 0; k < XBX_GetNumGameUsers(); ++ k )
|
|
{
|
|
bool bHeadset = false;
|
|
|
|
#ifdef _GAMECONSOLE
|
|
int iCtrlr = XBX_GetUserId( k );
|
|
|
|
if ( !XBX_GetUserIsGuest( k ) )
|
|
bHeadset = g_pMatchExtensions->GetIEngineVoice()->IsHeadsetPresent( iCtrlr );
|
|
#elif !defined( NO_STEAM )
|
|
bHeadset = g_pMatchExtensions->GetIEngineVoice()->IsHeadsetPresent( XBX_GetPrimaryUserId() );
|
|
#endif
|
|
|
|
char const *szCurValue = pMachine->GetString( CFmtStr( "player%d/voice", k ), "" );
|
|
char const *szHeadsetValue = bHeadset ? "headset" : "";
|
|
|
|
if ( szCurValue[0] != szHeadsetValue[0] )
|
|
{
|
|
KeyValues *msg = new KeyValues( "SysSession::VoiceStatus" );
|
|
KeyValues::AutoDelete autodelete_msg( msg );
|
|
|
|
XUID xuid = pMachine->GetUint64( CFmtStr( "player%d/xuid", k ), 0ull );
|
|
msg->SetUint64( "xuid", xuid );
|
|
msg->SetString( "voice", szHeadsetValue );
|
|
|
|
SendMessage( msg );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSysSessionBase::Voice_UpdateMutelist()
|
|
{
|
|
if ( IsServiceSession() )
|
|
return;
|
|
|
|
// Generate the mutelist and send it if it is different from the current settings
|
|
KeyValues *msg = new KeyValues( "SysSession::VoiceMutelist" );
|
|
KeyValues::AutoDelete autodelete_msg( msg );
|
|
|
|
msg->SetUint64( "xuid", m_xuidMachineId );
|
|
|
|
if ( KeyValues *pMembers = m_pSettings ? m_pSettings->FindKey( "members" ) : NULL )
|
|
{
|
|
int numMachines = pMembers->GetInt( "numMachines" );
|
|
for ( int i = 0; i < numMachines; ++ i )
|
|
{
|
|
XUID xuid = pMembers->GetUint64( CFmtStr( "machine%d/id", i ) );
|
|
if ( g_pMatchVoice->IsMachineMuted( xuid ) )
|
|
msg->SetUint64( CFmtStr( "Mutelist/%d", i ), xuid );
|
|
}
|
|
}
|
|
|
|
// Find current mutelist
|
|
KeyValues *pLocalMachine = NULL;
|
|
SessionMembersFindPlayer( m_pSettings, m_xuidMachineId, &pLocalMachine );
|
|
if ( pLocalMachine )
|
|
{
|
|
KeyValues *pCurMutelist = pLocalMachine->FindKey( "Mutelist" );
|
|
KeyValues *pNewMutelist = msg->FindKey( "Mutelist" );
|
|
|
|
if ( pCurMutelist && pNewMutelist )
|
|
{
|
|
for ( pCurMutelist = pCurMutelist->GetFirstValue(),
|
|
pNewMutelist = pNewMutelist->GetFirstValue();
|
|
pCurMutelist && pNewMutelist;
|
|
pCurMutelist = pCurMutelist->GetNextValue(),
|
|
pNewMutelist = pNewMutelist->GetNextValue() )
|
|
{
|
|
if ( pCurMutelist->GetUint64() != pNewMutelist->GetUint64() )
|
|
break;
|
|
}
|
|
}
|
|
if ( !pCurMutelist && !pNewMutelist )
|
|
// current and new mutelists compared equal
|
|
return;
|
|
}
|
|
|
|
SendMessage( msg );
|
|
}
|
|
|
|
|
|
void CSysSessionBase::OnPlayerLeave( XUID xuid )
|
|
{
|
|
}
|
|
|
|
bool CSysSessionBase::FindAndRemovePlayerFromMembers( XUID xuid )
|
|
{
|
|
KeyValues *pMembers = m_pSettings->FindKey( "members" );
|
|
Assert( pMembers );
|
|
if ( !pMembers )
|
|
return false;
|
|
|
|
int numMachines = pMembers->GetInt( "numMachines", 0 );
|
|
int numPlayers = pMembers->GetInt( "numPlayers", 0 );
|
|
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *pMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
|
|
if ( !pMachine )
|
|
continue;
|
|
|
|
int numOtherPlayers = pMachine->GetInt( "numPlayers", 0 );
|
|
for ( int j = 0; j < numOtherPlayers; ++ j )
|
|
{
|
|
XUID xuidOtherPlayer = pMachine->GetUint64( CFmtStr( "player%d/xuid", j ), 0ull );
|
|
if ( xuidOtherPlayer == xuid )
|
|
{
|
|
#ifdef _X360
|
|
// On X360 we need to update lobby members server-side count
|
|
MMX360_LobbyLeaveMembers( m_pSettings, m_lobby, k, k );
|
|
#endif
|
|
|
|
// We also need to remove talkers
|
|
Voice_ProcessTalkers( pMachine, false );
|
|
|
|
// The entire machine will be effectively disconnected
|
|
KeyValues::AutoDelete autodelete( pMachine );
|
|
pMembers->RemoveSubKey( pMachine );
|
|
|
|
for ( int kk = k + 1; kk < numMachines; ++ kk )
|
|
{
|
|
KeyValues *pNextMachine = pMembers->FindKey( CFmtStr( "machine%d", kk ) );
|
|
if ( !pNextMachine )
|
|
continue;
|
|
|
|
pNextMachine->SetName( CFmtStr( "machine%d", kk - 1 ) );
|
|
}
|
|
|
|
// Update counts
|
|
numMachines = MAX( 0, numMachines - 1 );
|
|
pMembers->SetInt( "numMachines", numMachines );
|
|
|
|
numPlayers = MAX( 0, numPlayers - numOtherPlayers );
|
|
pMembers->SetInt( "numPlayers", numPlayers );
|
|
|
|
// Now fire the events for the removed players
|
|
for ( j = 0; j < numOtherPlayers; ++ j )
|
|
{
|
|
KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", j ) );
|
|
xuidOtherPlayer = pPlayer->GetUint64( "xuid", 0ull );
|
|
|
|
KeyValues *kv = new KeyValues( "OnPlayerRemoved" );
|
|
kv->SetUint64( "xuid", xuidOtherPlayer );
|
|
if ( pPlayer )
|
|
{
|
|
KeyValues *pCopyPlayer = pPlayer->MakeCopy();
|
|
pCopyPlayer->SetName( "player" );
|
|
kv->AddSubKey( pCopyPlayer );
|
|
}
|
|
if ( pMachine )
|
|
{
|
|
KeyValues *pCopyMachine = pMachine->MakeCopy();
|
|
pCopyMachine->SetName( "machine" );
|
|
kv->AddSubKey( pCopyMachine );
|
|
}
|
|
OnSessionEvent( kv );
|
|
}
|
|
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
if ( dynamic_cast< CSysSessionHost * >( this ) )
|
|
{
|
|
// Update members information
|
|
LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "Members" ), false );
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CSysSessionBase::UpdateSessionProperties( KeyValues *kv, bool bHost )
|
|
{
|
|
#if !defined( NO_STEAM )
|
|
SetupSteamRankingConfiguration();
|
|
#endif
|
|
|
|
if ( !IsX360() && !bHost )
|
|
// On X360 every client must set their contexts, on PC it's
|
|
// all Steam-server-side and only host sets the metadata
|
|
return;
|
|
|
|
if ( IsX360() )
|
|
return;
|
|
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
if ( !m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
if ( kv )
|
|
{
|
|
LobbySetDataFromKeyValues( "system", kv->FindKey( "system" ) );
|
|
LobbySetDataFromKeyValues( "game", kv->FindKey( "game" ) );
|
|
LobbySetDataFromKeyValues( "options", kv->FindKey( "options" ) );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionBase::SetSessionActiveGameplayState( bool bActive, char const *szSecureServerAddress )
|
|
{
|
|
#ifdef _X360
|
|
MMX360_LobbySetActiveGameplayState( m_lobby, bActive, szSecureServerAddress );
|
|
#elif !defined( NO_STEAM )
|
|
switch ( m_lobby.m_eLobbyState )
|
|
{
|
|
case CSteamLobbyObject::STATE_DEFAULT:
|
|
if ( bActive )
|
|
{
|
|
m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_ACTIVE_GAME;
|
|
}
|
|
break;
|
|
|
|
case CSteamLobbyObject::STATE_ACTIVE_GAME:
|
|
if ( !bActive )
|
|
{
|
|
m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DEFAULT;
|
|
}
|
|
break;
|
|
|
|
case CSteamLobbyObject::STATE_DISCONNECTED_FROM_STEAM:
|
|
if ( !bActive )
|
|
{
|
|
// If active gameplay session has ended and we were disconnected from Steam
|
|
// then go back to main menu
|
|
m_lobby.m_eLobbyState = CSteamLobbyObject::STATE_DEFAULT;
|
|
Steam_OnServersDisconnected( NULL );
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void CSysSessionBase::UpdateTeamProperties( KeyValues *pTeamProperties )
|
|
{
|
|
#if defined (_X360)
|
|
#elif !defined(NO_STEAM)
|
|
|
|
if ( !m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
LobbySetDataFromKeyValues( "members", pTeamProperties->FindKey( "members" ) );
|
|
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionBase::UpdateServerInfo( KeyValues *pServerKey )
|
|
{
|
|
#if defined (_X360)
|
|
#elif !defined(NO_STEAM)
|
|
|
|
LobbySetDataFromKeyValues( "server", pServerKey->FindKey( "server" ) );
|
|
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionBase::PrintValue( KeyValues *val, char *chBuffer, int numBytesBuffer )
|
|
{
|
|
switch( val->GetDataType() )
|
|
{
|
|
case KeyValues::TYPE_INT:
|
|
Q_snprintf( chBuffer, numBytesBuffer, "%d", val->GetInt() );
|
|
break;
|
|
case KeyValues::TYPE_UINT64:
|
|
Q_snprintf( chBuffer, numBytesBuffer, "%llX", val->GetUint64() );
|
|
break;
|
|
case KeyValues::TYPE_FLOAT:
|
|
Q_snprintf( chBuffer, numBytesBuffer, "%f", val->GetFloat() );
|
|
break;
|
|
case KeyValues::TYPE_STRING:
|
|
Q_snprintf( chBuffer, numBytesBuffer, "%s", val->GetString() );
|
|
break;
|
|
default:
|
|
Warning( "Unknown type in CSysSessionHost::PrintValue ( %s )\n", val->GetName() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CSysSessionHost::CSysSessionHost( KeyValues *pSettings ) :
|
|
CSysSessionBase( pSettings ),
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
m_dblDormantMembersCheckTime( Plat_FloatTime() ),
|
|
m_numDormantMembersDetected( 0 ),
|
|
#endif
|
|
m_eState( STATE_INIT ),
|
|
m_flTimeOperationStarted( 0.0f ),
|
|
m_flInitializeTimestamp( 0.0f ),
|
|
m_teamResKey( 0ull ),
|
|
m_numRemainingTeamPlayers( 0 ),
|
|
m_flTeamResStartTime( 0.0f ),
|
|
m_ullCrypt( 0 )
|
|
{
|
|
}
|
|
|
|
CSysSessionHost::CSysSessionHost( CSysSessionClient *pClient, KeyValues *pSettings ) :
|
|
CSysSessionBase( pSettings ),
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
m_dblDormantMembersCheckTime( Plat_FloatTime() ),
|
|
m_numDormantMembersDetected( 0 ),
|
|
#endif
|
|
m_eState( STATE_IDLE ),
|
|
m_flTimeOperationStarted( 0.0f ),
|
|
m_flInitializeTimestamp( 0.0f ),
|
|
m_teamResKey( 0ull ),
|
|
m_numRemainingTeamPlayers( 0 ),
|
|
m_flTeamResStartTime( 0.0f ),
|
|
m_ullCrypt( 0 )
|
|
{
|
|
m_lobby = pClient->m_lobby;
|
|
|
|
m_Voice_flLastHeadsetStatusCheck = pClient->m_Voice_flLastHeadsetStatusCheck;
|
|
|
|
#ifdef _X360
|
|
|
|
m_pNetworkMgr = pClient->m_pNetworkMgr;
|
|
if ( m_pNetworkMgr )
|
|
m_pNetworkMgr->SetListener( this );
|
|
|
|
m_pAsyncOperation = pClient->m_pAsyncOperation;
|
|
Assert( !m_pAsyncOperation );
|
|
|
|
// If client was in migrate state, then disassociate the migrate call listener
|
|
if ( pClient->m_hLobbyMigrateCall )
|
|
{
|
|
MMX360_LobbyMigrateSetListener( pClient->m_hLobbyMigrateCall, NULL );
|
|
pClient->m_hLobbyMigrateCall = NULL;
|
|
}
|
|
|
|
// Schedule the host migration call
|
|
m_hLobbyMigrateCall = MMX360_LobbyMigrateHost( m_lobby, &m_MigrateCallState );
|
|
|
|
if ( !m_hLobbyMigrateCall )
|
|
{
|
|
// This is a dangerous notification because we are in the tree of constructors as
|
|
// follows: CMatchSessionOnlineHost -> CSysSessionHost
|
|
// The guideline to avoid troubles is to make sure that no code runs in the
|
|
// constructors of CMatchSessionOnlineHost and CSysSessionHost when the notification
|
|
// is fired.
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "migrate" );
|
|
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
return;
|
|
}
|
|
#elif !defined( NO_STEAM )
|
|
// Install callback for messages
|
|
m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
|
|
m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
|
|
|
|
// Set the migrated members information
|
|
LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false );
|
|
#endif
|
|
|
|
// Send a notification
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
|
|
kvEvent->SetString( "state", "host" );
|
|
kvEvent->SetUint64( "xuid", m_xuidMachineId );
|
|
#ifdef _X360
|
|
kvEvent->SetString( "migrate", "started" );
|
|
#endif
|
|
SendEventsNotification( kvEvent );
|
|
}
|
|
|
|
|
|
CSysSessionHost::~CSysSessionHost()
|
|
{
|
|
;
|
|
}
|
|
|
|
bool CSysSessionHost::Update()
|
|
{
|
|
if ( !CSysSessionBase::Update() )
|
|
return false;
|
|
|
|
switch( m_eState )
|
|
{
|
|
case STATE_INIT:
|
|
if ( !m_flInitializeTimestamp )
|
|
{
|
|
m_flInitializeTimestamp = Plat_FloatTime();
|
|
#if !defined( NO_STEAM )
|
|
SetupSteamRankingConfiguration();
|
|
#endif
|
|
}
|
|
if (
|
|
#if !defined( NO_STEAM )
|
|
( IsSteamRankingConfigured() || ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) &&
|
|
#endif
|
|
( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_delay_create_host.GetFloat() ) &&
|
|
SysSession_AllowCreate()
|
|
)
|
|
{
|
|
UpdateStateInit();
|
|
}
|
|
break;
|
|
|
|
#if !defined( NO_STEAM )
|
|
case STATE_IDLE:
|
|
// Track players who are in the MMS session object, but haven't made KV request to join or failed KV request and didn't drop
|
|
if ( m_lobby.m_uiLobbyID )
|
|
{
|
|
double dblTimeNow = Plat_FloatTime();
|
|
if ( dblTimeNow - m_dblDormantMembersCheckTime >= 15.0 ) // check every 15 seconds
|
|
{
|
|
m_dblDormantMembersCheckTime = dblTimeNow;
|
|
int numMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID );
|
|
int numDormantMembers = 0;
|
|
int nLimit = steamapicontext->SteamMatchmaking()->GetLobbyMemberLimit( m_lobby.m_uiLobbyID );
|
|
for ( int k = 0; nLimit && ( k < numMembers ); ++k )
|
|
{
|
|
XUID xuidMember = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex( m_lobby.m_uiLobbyID, k ).ConvertToUint64();
|
|
if ( !xuidMember )
|
|
continue;
|
|
|
|
// Check if this player is in the KV session
|
|
KeyValues *kvPlayer = SessionMembersFindPlayer( m_pSettings, xuidMember );
|
|
if ( kvPlayer )
|
|
continue; // member is properly authenticated, this is the common case
|
|
|
|
++ numDormantMembers;
|
|
}
|
|
m_numDormantMembersDetected = numDormantMembers;
|
|
int nNewLimit = m_numDormantMembersDetected + m_pSettings->GetInt( "members/numSlots", 1 );
|
|
if ( nLimit && nNewLimit != nLimit )
|
|
{
|
|
steamapicontext->SteamMatchmaking()->SetLobbyMemberLimit( m_lobby.m_uiLobbyID, nNewLimit );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef _X360
|
|
case STATE_ALLOWING_MIGRATE:
|
|
if ( mm_session_sys_timeout.GetFloat() + m_flTimeOperationStarted < Plat_FloatTime() )
|
|
{
|
|
// Assume that migration failed or we lost connection to Xbox LIVE
|
|
DestroyAfterMigrationFinished();
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
// Update reservation status
|
|
if ( m_teamResKey )
|
|
{
|
|
float time = Plat_FloatTime();
|
|
if ( time >= m_flTeamResStartTime + mm_session_team_res_timeout.GetFloat() )
|
|
{
|
|
UnreserveTeamSession();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSysSessionHost::Destroy()
|
|
{
|
|
// If we are migrating, then don't let the
|
|
// base class handle it because it will
|
|
// post Quit messages and leave the lobby...
|
|
if ( m_eState == STATE_MIGRATE )
|
|
{
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
#ifdef _X360
|
|
if ( m_eState == STATE_DELETE )
|
|
{
|
|
delete this;
|
|
return;
|
|
}
|
|
else if ( ShouldAllowX360HostMigration() )
|
|
{
|
|
m_eState = STATE_ALLOWING_MIGRATE;
|
|
m_flTimeOperationStarted = Plat_FloatTime();
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_DELETE;
|
|
}
|
|
#endif
|
|
|
|
// Chain to base which will "delete this"
|
|
CSysSessionBase::Destroy();
|
|
}
|
|
|
|
void CSysSessionHost::DebugPrint()
|
|
{
|
|
DevMsg( "CSysSessionHost [ state=%d ]\n", m_eState );
|
|
CSysSessionBase::DebugPrint();
|
|
}
|
|
|
|
XUID CSysSessionHost::GetHostXuid( XUID xuidValidResult )
|
|
{
|
|
#ifdef _X360
|
|
return m_xuidMachineId;
|
|
#elif !defined( NO_STEAM )
|
|
return m_lobby.m_uiLobbyID ? steamapicontext->SteamMatchmaking()
|
|
->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() : m_xuidMachineId;
|
|
#endif
|
|
return 0ull;
|
|
}
|
|
|
|
#ifdef _X360
|
|
void CSysSessionHost::GetHostSessionInfo( char chBuffer[ XSESSION_INFO_STRING_LENGTH ] )
|
|
{
|
|
MMX360_SessionInfoToString( m_lobby.m_xiInfo, chBuffer );
|
|
}
|
|
|
|
uint64 CSysSessionHost::GetHostSessionId()
|
|
{
|
|
return (const uint64&)(m_lobby.m_xiInfo.sessionID);
|
|
}
|
|
|
|
#endif
|
|
|
|
void CSysSessionHost::KickPlayer( KeyValues *pCommand )
|
|
{
|
|
XUID xuid = pCommand->GetUint64( "xuid", 0ull );
|
|
|
|
// Locate the machine being kicked
|
|
KeyValues *pMachine = NULL;
|
|
SessionMembersFindPlayer( m_pSettings, xuid, &pMachine );
|
|
if ( !pMachine )
|
|
return;
|
|
|
|
// Use machine primary xuid
|
|
xuid = pMachine->GetUint64( "id" );
|
|
if ( xuid == GetHostXuid() )
|
|
{
|
|
DevWarning( "CSysSessionHost::Kick unsupported for host xuid!\n" );
|
|
return;
|
|
}
|
|
|
|
// Notify everybody that a machine is being kicked
|
|
KeyValues *notify = new KeyValues( "SysSession::OnPlayerKicked" );
|
|
KeyValues::AutoDelete autodelete_notify( notify );
|
|
|
|
notify->SetUint64( "xuid", xuid );
|
|
|
|
SendMessage( notify );
|
|
|
|
// Remove player information from our records
|
|
FindAndRemovePlayerFromMembers( xuid );
|
|
|
|
// Remember the player in our kicked players map
|
|
m_mapKickedPlayers.InsertOrReplace( xuid, Plat_FloatTime() );
|
|
|
|
#if !defined( _X360 ) && !defined( NO_STEAM )
|
|
// Forcefully kick the player from the lobby
|
|
// steamapicontext->SteamMatchmaking()->???
|
|
// On X360 we just forcefully shutdown that client's
|
|
// network channel and all peers do the same, so the kicked
|
|
// client is indeed kicked no matter how smartly he tries to stay.
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionHost::OnUpdateSessionSettings( KeyValues *kv )
|
|
{
|
|
KeyValues *kvPropertiesUpdated = kv->FindKey( "update", false );
|
|
if ( !kvPropertiesUpdated )
|
|
kvPropertiesUpdated = kv->FindKey( "delete", false );
|
|
UpdateSessionProperties( kvPropertiesUpdated );
|
|
|
|
// Now send the message to everybody about the session update
|
|
KeyValues *notify = new KeyValues( "SysSession::OnUpdate" );
|
|
KeyValues::AutoDelete autodelete_notify( notify );
|
|
|
|
if ( KeyValues *kvUpdate = kv->FindKey( "update" ) )
|
|
notify->AddSubKey( kvUpdate->MakeCopy() );
|
|
if ( KeyValues *kvDelete = kv->FindKey( "delete" ) )
|
|
notify->AddSubKey( kvDelete->MakeCopy() );
|
|
|
|
SendMessage( notify );
|
|
}
|
|
|
|
void CSysSessionHost::OnPlayerUpdated( KeyValues *pPlayer )
|
|
{
|
|
// Notify the framework about player updated
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
|
|
kvEvent->SetUint64( "xuid", pPlayer->GetUint64( "xuid" ) );
|
|
kvEvent->SetPtr( "player", pPlayer );
|
|
OnSessionEvent( kvEvent );
|
|
|
|
// Now send the message
|
|
KeyValues *notify = new KeyValues( "SysSession::OnPlayerUpdated" );
|
|
KeyValues::AutoDelete autodelete( notify );
|
|
notify->MergeFrom( pPlayer, KeyValues::MERGE_KV_UPDATE );
|
|
|
|
SendMessage( notify );
|
|
}
|
|
|
|
void CSysSessionHost::OnMachineUpdated( KeyValues *pMachine )
|
|
{
|
|
// Send the message to peers
|
|
KeyValues *notify = new KeyValues( "SysSession::OnMachineUpdated" );
|
|
KeyValues::AutoDelete autodelete( notify );
|
|
notify->MergeFrom( pMachine, KeyValues::MERGE_KV_UPDATE );
|
|
|
|
SendMessage( notify );
|
|
}
|
|
|
|
void CSysSessionHost::UpdateStateInit()
|
|
{
|
|
#ifdef _X360
|
|
MMX360_LobbyCreate( m_pSettings, &m_pAsyncOperation );
|
|
#elif !defined( NO_STEAM )
|
|
ELobbyType eType = k_ELobbyTypeFriendsOnly;
|
|
int numSlots = m_pSettings->GetInt( "members/numSlots", 1 );
|
|
|
|
SteamAPICall_t hCall = steamapicontext->SteamMatchmaking()->CreateLobby( eType, numSlots );
|
|
m_CallbackOnLobbyCreated.Set( hCall, this, &CSysSessionHost::Steam_OnLobbyCreated );
|
|
#endif
|
|
|
|
m_eState = STATE_CREATING;
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
void CSysSessionHost::OnAsyncOperationFinished()
|
|
{
|
|
if ( m_eState == STATE_CREATING )
|
|
{
|
|
if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED )
|
|
{
|
|
Warning( "CSysSessionHost: CreateSession failed. Error %d\n", m_pAsyncOperation->GetResult() );
|
|
ReleaseAsyncOperation();
|
|
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "create" );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We have successfully created the lobby,
|
|
// retrieve all information from the async creation.
|
|
//
|
|
m_lobby = m_pAsyncOperation->GetLobby();
|
|
ReleaseAsyncOperation();
|
|
|
|
// Expose the NONCE for all future clients of the session
|
|
m_pSettings->SetUint64( "system/nonce", m_lobby.m_uiNonce );
|
|
|
|
// Setup our xnaddr
|
|
char chXnaddr[ XNADDR_STRING_LENGTH ] = {0};
|
|
MMX360_XnaddrToString( m_lobby.m_xiInfo.hostAddress, chXnaddr );
|
|
m_pSettings->SetString( "members/machine0/xnaddr", chXnaddr );
|
|
|
|
InitSessionProperties();
|
|
|
|
// Create the network mgr
|
|
m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() );
|
|
|
|
// Setup local Xbox 360 voice
|
|
Voice_ProcessTalkers( NULL, true );
|
|
|
|
m_eState = STATE_IDLE;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
else if ( m_eState == STATE_DELETE )
|
|
{
|
|
ReleaseAsyncOperation();
|
|
SysSession360_RegisterPending( this );
|
|
}
|
|
else
|
|
{
|
|
ReleaseAsyncOperation();
|
|
}
|
|
}
|
|
|
|
void CSysSessionHost::OnX360NetDisconnected( XUID xuidRemote )
|
|
{
|
|
if ( m_eState == STATE_DELETE || m_eState == STATE_ALLOWING_MIGRATE )
|
|
return;
|
|
|
|
CSysSessionBase::OnX360NetDisconnected( xuidRemote );
|
|
}
|
|
|
|
bool CSysSessionHost::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg )
|
|
{
|
|
if ( !Q_stricmp( msg->GetName(), "SysSession::TeamReservation" ) )
|
|
{
|
|
XUID key = msg->GetUint64( "teamResKey" );
|
|
int teamSize = msg->GetInt( "numPlayers" );
|
|
|
|
DevMsg( "Received reservation msg: res key = %llx, numPlayers = %d\n", key, teamSize );
|
|
|
|
Process_TeamReservation( key, teamSize );
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( m_eState == STATE_IDLE && !Q_stricmp( msg->GetName(), "SysSession::RequestJoinData" ) )
|
|
{
|
|
// Check sessionid the client is trying to connect to
|
|
uint64 uiSessionIdRequest = msg->GetUint64( "sessionid" );
|
|
if ( uiSessionIdRequest != ( const uint64 & ) m_lobby.m_xiInfo.sessionID )
|
|
return false;
|
|
|
|
// This is a legit connectionless packet requesting to join the session
|
|
XUID machineid = msg->GetUint64( "id" );
|
|
XNKID xnkidSession = m_lobby.m_xiInfo.sessionID;
|
|
|
|
if ( !m_pNetworkMgr->ConnectionPeerOpenPassive( machineid, pkt, &xnkidSession ) )
|
|
{
|
|
DevWarning( "CSysSessionHost discarding SysSession::RequestJoinData due to passive connection failure!\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( !Process_RequestJoinData( machineid, msg->FindKey( "settings" ) ) )
|
|
m_pNetworkMgr->ConnectionPeerClose( machineid );
|
|
|
|
return true;
|
|
}
|
|
|
|
// Unknown packet, permanently block sender
|
|
return false;
|
|
}
|
|
|
|
void CSysSessionHost::DestroyAfterMigrationFinished()
|
|
{
|
|
// Picking up slack from CSysSessionBase::Destroy scenario
|
|
|
|
if ( m_pNetworkMgr )
|
|
{
|
|
m_pNetworkMgr->Destroy();
|
|
m_pNetworkMgr = NULL;
|
|
}
|
|
|
|
m_eState = STATE_DELETE;
|
|
MMX360_LobbyDelete( m_lobby, &m_pAsyncOperation );
|
|
}
|
|
|
|
#endif
|
|
|
|
void CSysSessionHost::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
|
|
{
|
|
char const *szMsg = msg->GetName();
|
|
|
|
// Parse the message depending on our state
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_IDLE:
|
|
if ( !Q_stricmp( "SysSession::RequestJoinData", szMsg ) )
|
|
{
|
|
XUID machineid = msg->GetUint64( "id" );
|
|
KeyValues *pSettings = msg->FindKey( "settings" );
|
|
|
|
if ( machineid != xuidSrc )
|
|
return;
|
|
|
|
Process_RequestJoinData( xuidSrc, pSettings );
|
|
}
|
|
else if ( !bValidatedLobbyMember )
|
|
{
|
|
return;
|
|
}
|
|
else if ( !Q_stricmp( "SysSession::VoiceStatus", szMsg ) )
|
|
{
|
|
Process_VoiceStatus( msg, xuidSrc );
|
|
}
|
|
#ifdef _X360 // (CS:GO 2017) -- these are old X360 flows that we no longer care to support
|
|
else if ( !Q_stricmp( "SysSession::VoiceMutelist", szMsg ) )
|
|
{
|
|
Process_VoiceMutelist( msg );
|
|
}
|
|
else if ( !Q_stricmp( "SysSession::TeamReservation", szMsg ) )
|
|
{
|
|
XUID key = msg->GetUint64( "teamResKey" );
|
|
int teamSize = msg->GetInt( "numPlayers" );
|
|
|
|
Process_TeamReservation( key, teamSize );
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
|
|
}
|
|
return;
|
|
|
|
#ifdef _X360
|
|
case STATE_ALLOWING_MIGRATE:
|
|
if ( !Q_stricmp( szMsg, "SysSession::HostMigrated" ) )
|
|
{
|
|
// another peer picked up the session, bail out
|
|
DestroyAfterMigrationFinished();
|
|
}
|
|
return;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void CSysSessionHost::Migrate( KeyValues *pCommand )
|
|
{
|
|
if ( Q_stricmp( pCommand->GetString( "migrate" ), "host>client" ) )
|
|
{
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
#ifndef NO_STEAM
|
|
Verify( steamapicontext->SteamMatchmaking()->SetLobbyOwner( m_lobby.m_uiLobbyID, pCommand->GetUint64( "xuid" ) ) );
|
|
|
|
// Prepare the update notification
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
m_eState = STATE_MIGRATE;
|
|
kv->SetString( "action", "client" );
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionHost::OnPlayerLeave( XUID xuid )
|
|
{
|
|
// We detected that the player dropped out of the lobby,
|
|
// disconnect the player from the session
|
|
|
|
#if !defined( NO_STEAM )
|
|
if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) )
|
|
{
|
|
DevMsg( "CSysSessionHost::OnPlayerLeave(%llx) ignored in noleave mode.\n", xuid );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if ( FindAndRemovePlayerFromMembers( xuid ) )
|
|
{
|
|
// Now send the message to everybody about the session update
|
|
KeyValues *reply = new KeyValues( "SysSession::OnPlayerRemoved" );
|
|
KeyValues::AutoDelete autodelete_reply( reply );
|
|
reply->SetUint64( "xuid", xuid );
|
|
SendMessage( reply );
|
|
|
|
// Fire the notification about a machine disconnected from the session
|
|
if ( KeyValues *kvEvent = new KeyValues( "OnPlayerMachinesDisconnected" ) )
|
|
{
|
|
kvEvent->SetInt( "numMachines", 1 );
|
|
kvEvent->SetUint64( "id", xuid );
|
|
OnSessionEvent( kvEvent );
|
|
}
|
|
|
|
#ifdef _X360
|
|
// Send this down to the engine as well
|
|
if ( m_lobby.m_bXSessionStarted )
|
|
{
|
|
// Send a message to the server that we need to remove this player
|
|
KeyValues *pDisconnectRequest = new KeyValues( "OnPlayerRemovedFromSession" );
|
|
pDisconnectRequest->SetUint64( "xuid", xuid );
|
|
g_pMatchExtensions->GetIVEngineClient()->ServerCmdKeyValues( pDisconnectRequest );
|
|
}
|
|
#endif // _X360
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
|
|
void CSysSessionHost::Steam_OnLobbyCreated( LobbyCreated_t *pLobbyCreate, bool bError )
|
|
{
|
|
if ( bError || pLobbyCreate->m_eResult != k_EResultOK )
|
|
{
|
|
Warning( "CSysSessionHost: CreateSession failed. Error %d\n", bError ? -1 : pLobbyCreate->m_eResult );
|
|
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "create" );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_lobby.m_uiLobbyID = pLobbyCreate->m_ulSteamIDLobby;
|
|
|
|
DevMsg( "Created lobby id: 0x%llx\n", m_lobby.m_uiLobbyID );
|
|
|
|
// will get a subsequent callback at at Steam_OnLobbyEntered to indicate that we are a lobby member
|
|
m_CallbackOnLobbyEntered.Register( this, &CSysSessionHost::Steam_OnLobbyEntered );
|
|
}
|
|
}
|
|
|
|
void CSysSessionHost::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter )
|
|
{
|
|
// Filter out notifications not from our lobby
|
|
if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
m_CallbackOnLobbyEntered.Unregister();
|
|
|
|
if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
|
|
{
|
|
Warning( "Matchmaking: lobby response %d!\n", pLobbyEnter->m_EChatRoomEnterResponse );
|
|
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", LobbyEnterErrorAsString( pLobbyEnter ) );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Set all the properties of the new session
|
|
InitSessionProperties();
|
|
|
|
// Install callback for messages
|
|
m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
|
|
m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
|
|
|
|
// Setup voice
|
|
Voice_ProcessTalkers( NULL, true );
|
|
|
|
m_eState = STATE_IDLE;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool CSysSessionHost::GetLobbyType( KeyValues *kv, ELobbyType *peType, bool *pbJoinable )
|
|
{
|
|
if ( !peType || !pbJoinable )
|
|
return false;
|
|
|
|
char const *szLock = kv->GetString( "system/lock", NULL );
|
|
char const *szAccess = kv->GetString( "system/access", NULL );
|
|
if ( !szAccess && !szLock )
|
|
return false;
|
|
|
|
if ( !szLock )
|
|
szLock = m_pSettings->GetString( "system/lock", "" );
|
|
if ( !szAccess )
|
|
szAccess = m_pSettings->GetString( "system/access", "public" );
|
|
|
|
*pbJoinable = ( szLock[0] == 0 ); // non joinable if locked
|
|
|
|
if ( !Q_stricmp( "public", szAccess ) )
|
|
return *peType = k_ELobbyTypePublic, true;
|
|
else if ( !Q_stricmp( "friends", szAccess ) )
|
|
return *peType = k_ELobbyTypeFriendsOnly, true;
|
|
else if ( !Q_stricmp( "private", szAccess ) )
|
|
return *peType = k_ELobbyTypePrivate, true; // private
|
|
else
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
void CSysSessionHost::UpdateMembersInfo()
|
|
{
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
if ( m_lobby.m_uiLobbyID )
|
|
{
|
|
steamapicontext->SteamMatchmaking()->SetLobbyMemberLimit( m_lobby.m_uiLobbyID,
|
|
m_pSettings->GetInt( "members/numSlots", 1 ) + m_numDormantMembersDetected );
|
|
}
|
|
|
|
// Set the initial members information
|
|
LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false );
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionHost::InitSessionProperties()
|
|
{
|
|
UpdateMembersInfo();
|
|
UpdateSessionProperties( m_pSettings );
|
|
}
|
|
|
|
void CSysSessionHost::UpdateSessionProperties( KeyValues *kv )
|
|
{
|
|
if ( !kv )
|
|
return;
|
|
|
|
CSysSessionBase::UpdateSessionProperties( kv, true );
|
|
|
|
//
|
|
// Set joinability and public/private slots distribution
|
|
//
|
|
|
|
#ifdef _X360
|
|
OnX360AllSessionMembersJoinLeave( kv );
|
|
#elif !defined( NO_STEAM )
|
|
ELobbyType eType = k_ELobbyTypePublic;
|
|
bool bJoinable = true;
|
|
if ( GetLobbyType( kv, &eType, &bJoinable ) && m_lobby.m_uiLobbyID )
|
|
{
|
|
steamapicontext->SteamMatchmaking()->SetLobbyType( m_lobby.m_uiLobbyID, eType );
|
|
steamapicontext->SteamMatchmaking()->SetLobbyJoinable( m_lobby.m_uiLobbyID, bJoinable );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Update QOS reply data of the session
|
|
//
|
|
#ifdef _X360
|
|
if ( IsX360() && !m_hLobbyMigrateCall )
|
|
{
|
|
CUtlBuffer bufQosData;
|
|
bufQosData.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
g_pMatchFramework->GetMatchNetworkMsgController()->PackageGameDetailsForQOS( m_pSettings, bufQosData );
|
|
g_pMatchExtensions->GetIXOnline()->XNetQosListen( &m_lobby.m_xiInfo.sessionID,
|
|
( const BYTE * ) bufQosData.Base(), bufQosData.TellMaxPut(),
|
|
0, XNET_QOS_LISTEN_SET_DATA );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool CSysSessionHost::Process_RequestJoinData( XUID xuidClient, KeyValues *pSettings )
|
|
{
|
|
// Check if the request is a duplicate because of migration and client had
|
|
// to re-request join authorization from the new host, but the old host
|
|
// already included the new client into the session...
|
|
if ( SessionMembersFindPlayer( m_pSettings, xuidClient ) )
|
|
return true;
|
|
|
|
// We should merge the client's information into our settings information
|
|
int numUsersConnecting = pSettings->GetInt( "members/numPlayers", 0 );
|
|
int numMachinesConnecting = pSettings->GetInt( "members/numMachines", 0 );
|
|
|
|
int numSlotsTotal = m_pSettings->GetInt( "members/numSlots", 0 );
|
|
int numUsersCurrent = m_pSettings->GetInt( "members/numPlayers", 0 );
|
|
int numMachinesCurrent = m_pSettings->GetInt( "members/numMachines", 0 );
|
|
|
|
// CS:GO 2017 - Validate a bunch of parameters to match the authenticated XUID:
|
|
if ( numMachinesConnecting != 1 ) return false;
|
|
if ( numUsersConnecting != 1 ) return false;
|
|
if ( pSettings->GetInt( "members/machine0/numPlayers" ) != 1 ) return false;
|
|
if ( pSettings->GetUint64( "members/machine0/id" ) != xuidClient ) return false;
|
|
if ( pSettings->GetUint64( "members/machine0/player0/xuid" ) != xuidClient ) return false;
|
|
|
|
// Check if there are more players on the server than in session
|
|
INetSupport::ClientInfo_t nsci = {0};
|
|
g_pMatchExtensions->GetINetSupport()->GetClientInfo( &nsci );
|
|
|
|
// If we represent a team lobby, then only consider players on our team
|
|
if ( nsci.m_numHumanPlayers &&
|
|
!Q_stricmp( m_pSettings->GetString( "system/netflag", "" ), "teamlobby" ) )
|
|
{
|
|
// TODO: int nMatchTeam = m_pSettings->GetInt( "server/team", 1 );
|
|
// for now just always go by Steam lobby slots
|
|
nsci.m_numHumanPlayers = 0;
|
|
}
|
|
|
|
// Prepare the reply package
|
|
KeyValues *reply = new KeyValues( "SysSession::ReplyJoinData" );
|
|
KeyValues::AutoDelete autodelete( reply );
|
|
|
|
reply->SetUint64( "id", xuidClient );
|
|
|
|
// If we have a team reservation in place then reject clients that don't
|
|
// provide the reservation key
|
|
if ( m_teamResKey )
|
|
{
|
|
uint64 clientKey = pSettings->GetUint64( "teamResKey", 0 );
|
|
if ( m_teamResKey != clientKey)
|
|
{
|
|
reply->SetString( "error", "TeamResFail" );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// One more PWF team client has joined the session
|
|
m_numRemainingTeamPlayers--;
|
|
if ( m_numRemainingTeamPlayers == 0 )
|
|
{
|
|
UnreserveTeamSession();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If any of the data is malformed, early out
|
|
if ( !numUsersConnecting || !numMachinesConnecting ||
|
|
!numSlotsTotal || !numUsersCurrent || !numMachinesCurrent )
|
|
{
|
|
reply->SetString( "error", "n/a" );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
|
|
// If the session is using a private key (tournament lobby), then keys must match
|
|
char const *szRequestLockField = pSettings->GetString( "system/lock", "" );
|
|
if ( mm_session_sys_pkey.GetString()[0] )
|
|
{
|
|
if ( V_strcmp( szRequestLockField, mm_session_sys_pkey.GetString() ) )
|
|
{
|
|
DevMsg( "LOBBY: blocking join request from %llu with invalid key: %s\n", xuidClient, szRequestLockField );
|
|
reply->SetString( "error", "n/a" );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If the session is locked, prevent join
|
|
char const *szSessionSystemSettingsLock = m_pSettings->GetString( "system/lock", "" );
|
|
if ( szSessionSystemSettingsLock[0] )
|
|
{
|
|
char const *szReplyError = "lock";
|
|
if ( !V_stricmp( "mmqueue", szSessionSystemSettingsLock ) )
|
|
{
|
|
szReplyError = "LockMmQueue";
|
|
}
|
|
reply->SetString( "error", szReplyError );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
|
|
// If the request is a soft-join then check privacy and mmqueue
|
|
if ( KeyValues *kvSoftChecks = pSettings->FindKey( "joincheck" ) )
|
|
{
|
|
FOR_EACH_SUBKEY( kvSoftChecks, kvSubCheck )
|
|
{
|
|
char const *szSoftValue = kvSubCheck->GetName();
|
|
char const *szSoftKey = kvSubCheck->GetString();
|
|
bool bSoftCheckOK = ( !V_strcmp( "#empty#", szSoftValue ) && !*m_pSettings->GetString( szSoftKey ) ) ||
|
|
( ( *szSoftValue == '[' ) && V_strstr( szSoftValue, CFmtStr( "[%s]", m_pSettings->GetString( szSoftKey ) ) ) ) ||
|
|
!V_stricmp( m_pSettings->GetString( szSoftKey ), szSoftValue );
|
|
if ( !bSoftCheckOK )
|
|
{
|
|
DevMsg( "LOBBY: blocking join request from %llu with check: %s = '%s' (actual: '%s')\n", xuidClient,
|
|
szSoftKey, szSoftValue, m_pSettings->GetString( szSoftKey ) );
|
|
char const *szReplyError = "lock";
|
|
reply->SetString( "error", szReplyError );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the user has previously been kicked then do not let them re-join
|
|
int idxKicked = m_mapKickedPlayers.Find( xuidClient );
|
|
if ( idxKicked != m_mapKickedPlayers.InvalidIndex() )
|
|
{
|
|
if ( Plat_FloatTime() - m_mapKickedPlayers.Element( idxKicked ) < mm_session_sys_kick_ban_duration.GetFloat() )
|
|
{
|
|
DevMsg( "LOBBY: blocking join request from %llu, still %.1f sec kick ban duration remaining, mm_session_sys_kick_ban_duration = %.1f.\n", xuidClient,
|
|
m_mapKickedPlayers.Element( idxKicked ) + 1 + mm_session_sys_kick_ban_duration.GetFloat() - Plat_FloatTime(), mm_session_sys_kick_ban_duration.GetFloat() );
|
|
reply->SetString( "error", "kicked" );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If the session is full, early out
|
|
if ( MAX( numUsersCurrent, nsci.m_numHumanPlayers )
|
|
+ numUsersConnecting > numSlotsTotal )
|
|
{
|
|
reply->SetString( "error", "full" );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
|
|
// Get the members containers
|
|
KeyValues *pMembers = m_pSettings->FindKey( "members" );
|
|
Assert( pMembers );
|
|
if ( !pMembers )
|
|
return false;
|
|
|
|
KeyValues *pMembersConnecting = pSettings->FindKey( "members" );
|
|
Assert( pMembersConnecting );
|
|
if ( !pMembersConnecting )
|
|
return false;
|
|
|
|
// Validate that the connecting machines have the required TU and DLC installed
|
|
for ( int k = 0; k < numMachinesConnecting; ++ k )
|
|
{
|
|
KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) );
|
|
Assert( kvConnecting );
|
|
if ( !kvConnecting )
|
|
continue;
|
|
|
|
char const *szTuString = kvConnecting->GetString( "tuver" );
|
|
uint64 uiDlcMask = kvConnecting->GetUint64( "dlcmask" );
|
|
|
|
char const *szTuRequired = pMembers->GetString( "machine0/tuver" );
|
|
uint64 uiDlcRequired = m_pSettings->GetUint64( "game/dlcrequired" );
|
|
|
|
if ( Q_strcmp( szTuString, szTuRequired ) )
|
|
{
|
|
reply->SetString( "error", "turequired" );
|
|
reply->SetString( "turequired", szTuRequired );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
|
|
if ( ( uiDlcMask & uiDlcRequired ) != uiDlcRequired )
|
|
{
|
|
reply->SetString( "error", "dlcrequired" );
|
|
reply->SetUint64( "dlcrequired", uiDlcRequired &~uiDlcMask );
|
|
reply->SetUint64( "dlcmask", uiDlcRequired );
|
|
SendMessage( reply );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Merge the information about the connecting members
|
|
for ( int k = 0; k < numMachinesConnecting; ++ k )
|
|
{
|
|
KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) );
|
|
Assert( kvConnecting );
|
|
if ( !kvConnecting )
|
|
continue;
|
|
|
|
KeyValues *kvNewMachine = kvConnecting->MakeCopy();
|
|
kvNewMachine->SetName( CFmtStr( "machine%d", k + numMachinesCurrent ) );
|
|
pMembers->AddSubKey( kvNewMachine );
|
|
|
|
// Register talkers from that machine
|
|
Voice_ProcessTalkers( kvNewMachine, true );
|
|
}
|
|
|
|
// Update the current count of machines and members
|
|
pMembers->SetInt( "numMachines", numMachinesCurrent + numMachinesConnecting );
|
|
pMembers->SetInt( "numPlayers", numUsersCurrent + numUsersConnecting );
|
|
|
|
// Join flags
|
|
pMembers->SetUint64( "joinflags", pMembersConnecting->GetUint64( "joinflags" ) );
|
|
|
|
#ifdef _X360
|
|
// On X360 we need to update lobby members server-side count
|
|
MMX360_LobbyJoinMembers( pSettings, m_lobby );
|
|
#endif
|
|
|
|
// Fire the notifications about a bunch of machines connected to the session
|
|
if ( KeyValues *kvEvent = new KeyValues( "OnPlayerMachinesConnected" ) )
|
|
{
|
|
kvEvent->SetInt( "numMachines", numMachinesConnecting );
|
|
kvEvent->SetUint64( "id", xuidClient );
|
|
OnSessionEvent( kvEvent );
|
|
}
|
|
|
|
// Fire the notifications about the new players
|
|
for ( int k = 0; k < numMachinesConnecting; ++ k )
|
|
{
|
|
KeyValues *kvConnecting = pMembersConnecting->FindKey( CFmtStr( "machine%d", k ) );
|
|
if ( !kvConnecting )
|
|
continue;
|
|
|
|
int numPlayers = kvConnecting->GetInt( "numPlayers", 0 );
|
|
for ( int j = 0; j < numPlayers; ++ j )
|
|
{
|
|
KeyValues *pPlayer = kvConnecting->FindKey( CFmtStr( "player%d", j ) );
|
|
XUID xuid = pPlayer->GetUint64( "xuid", 0ull );
|
|
if ( !xuid )
|
|
continue;
|
|
|
|
if ( KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" ) )
|
|
{
|
|
kvEvent->SetUint64( "xuid", xuid );
|
|
kvEvent->SetString( "state", "joined" );
|
|
kvEvent->SetPtr( "player", pPlayer );
|
|
OnSessionEvent( kvEvent );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send the encryption key to the connected client too
|
|
reply->SetUint64( "crypt", m_ullCrypt );
|
|
|
|
// After everything settled with the new players on this machine,
|
|
// notify everybody of the new state of the session
|
|
reply->AddSubKey( m_pSettings->MakeCopy() );
|
|
SendMessage( reply );
|
|
|
|
Voice_UpdateMutelist();
|
|
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
// Update members information
|
|
LobbySetDataFromKeyValues( "members", m_pSettings->FindKey( "members" ), false );
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSysSessionHost::Process_TeamReservation( XUID key, int teamSize )
|
|
{
|
|
KeyValues *pMembers;
|
|
int numPlayers;
|
|
int numSlots;
|
|
|
|
KeyValues *reply = new KeyValues( "SysSession::TeamReservationResult" );
|
|
KeyValues::AutoDelete autodelete( reply );
|
|
|
|
// Check we are not already reserved
|
|
if ( m_teamResKey != 0 )
|
|
{
|
|
reply->SetString( "result", "alreadyReserved" );
|
|
goto xit;
|
|
}
|
|
|
|
// Check if we have enough free slots
|
|
pMembers = m_pSettings->FindKey( "members" );
|
|
numPlayers = pMembers->GetInt( "numPlayers" );
|
|
numSlots = pMembers->GetInt( "numSlots" );
|
|
|
|
if ( numSlots < numPlayers + teamSize)
|
|
{
|
|
reply->SetString( "result", "notEnoughSlots" );
|
|
goto xit;
|
|
}
|
|
|
|
reply->SetString( "result", "success" );
|
|
ReserveTeamSession( key, teamSize );
|
|
|
|
xit:
|
|
SendMessage( reply );
|
|
}
|
|
|
|
void CSysSessionHost::ReserveTeamSession( XUID key, int numPlayers )
|
|
{
|
|
#if !defined (NO_STEAM)
|
|
DevMsg( "CSysSessionHost::ReserveTeamSession\n");
|
|
m_teamResKey = key;
|
|
m_numRemainingTeamPlayers = numPlayers;
|
|
m_flTeamResStartTime = Plat_FloatTime();
|
|
|
|
// Set lobby data
|
|
KeyValues *settings = new KeyValues( "TeamReservation" );
|
|
KeyValues::AutoDelete autodelete( settings );
|
|
settings->SetInt( "TeamRes", 1 );
|
|
LobbySetDataFromKeyValues( "TeamReservation", settings );
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionHost::UnreserveTeamSession()
|
|
{
|
|
#if !defined (NO_STEAM)
|
|
DevMsg( "CSysSessionHost::UnreserveTeamSession\n");
|
|
m_teamResKey = 0;
|
|
m_numRemainingTeamPlayers = 0;
|
|
|
|
// Set lobby data
|
|
KeyValues *settings = new KeyValues( "TeamReservation" );
|
|
KeyValues::AutoDelete autodelete( settings );
|
|
settings->SetInt( "TeamRes", 0 );
|
|
LobbySetDataFromKeyValues( "TeamReservation", settings );
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionHost::Process_VoiceStatus( KeyValues *msg, XUID xuidSrc )
|
|
{
|
|
XUID xuid = msg->GetUint64( "xuid" );
|
|
Assert( xuid == xuidSrc );
|
|
if ( xuid != xuidSrc )
|
|
return;
|
|
|
|
KeyValues *pPlayer = SessionMembersFindPlayer( m_pSettings, xuid );
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
char const *szValue = msg->GetString( "voice" );
|
|
pPlayer->SetString( "voice", szValue );
|
|
|
|
OnPlayerUpdated( pPlayer );
|
|
}
|
|
|
|
void CSysSessionHost::Process_VoiceMutelist( KeyValues *msg )
|
|
{
|
|
XUID xuid = msg->GetUint64( "xuid" );
|
|
|
|
KeyValues *pMachine = NULL;
|
|
SessionMembersFindPlayer( m_pSettings, xuid, &pMachine );
|
|
if ( !pMachine )
|
|
return;
|
|
|
|
// Remove old mutelist
|
|
if ( KeyValues *pMutelist = pMachine->FindKey( "Mutelist" ) )
|
|
{
|
|
pMachine->RemoveSubKey( pMutelist );
|
|
pMutelist->deleteThis();
|
|
}
|
|
|
|
// Update new mutelist
|
|
if ( KeyValues *pMutelist = msg->FindKey( "Mutelist" ) )
|
|
{
|
|
pMachine->FindKey( "Mutelist", true )->MergeFrom( pMutelist, KeyValues::MERGE_KV_UPDATE );
|
|
}
|
|
|
|
OnMachineUpdated( pMachine );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Client session implementation
|
|
//
|
|
|
|
|
|
CSysSessionClient::CSysSessionClient( KeyValues *pSettings ) :
|
|
CSysSessionBase( pSettings ),
|
|
m_eState( STATE_INIT ),
|
|
m_flInitializeTimestamp( 0.0f )
|
|
{
|
|
#ifdef _X360
|
|
memset( &m_xnaddrLocal, 0, sizeof( m_xnaddrLocal ) );
|
|
#endif
|
|
}
|
|
|
|
CSysSessionClient::CSysSessionClient( CSysSessionHost *pHost, KeyValues *pSettings ) :
|
|
CSysSessionBase( pSettings ),
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
#endif
|
|
m_eState( STATE_IDLE ),
|
|
m_flInitializeTimestamp( 0.0f )
|
|
{
|
|
m_lobby = pHost->m_lobby;
|
|
|
|
m_Voice_flLastHeadsetStatusCheck = pHost->m_Voice_flLastHeadsetStatusCheck;
|
|
|
|
#ifdef _X360
|
|
|
|
m_pNetworkMgr = pHost->m_pNetworkMgr;
|
|
if ( m_pNetworkMgr )
|
|
m_pNetworkMgr->SetListener( this );
|
|
|
|
m_pAsyncOperation = pHost->m_pAsyncOperation;
|
|
Assert( !m_pAsyncOperation );
|
|
|
|
// If client was in migrate state, then disassociate the migrate call listener
|
|
if ( pHost->m_hLobbyMigrateCall )
|
|
{
|
|
MMX360_LobbyMigrateSetListener( pHost->m_hLobbyMigrateCall, NULL );
|
|
pHost->m_hLobbyMigrateCall = NULL;
|
|
}
|
|
|
|
#elif !defined( NO_STEAM )
|
|
// Install callback for messages
|
|
m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
|
|
m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
CSysSessionClient::~CSysSessionClient()
|
|
{
|
|
;
|
|
}
|
|
|
|
bool CSysSessionClient::Update()
|
|
{
|
|
if ( !CSysSessionBase::Update() )
|
|
return false;
|
|
|
|
switch( m_eState )
|
|
{
|
|
case STATE_INIT:
|
|
if ( !m_flInitializeTimestamp )
|
|
{
|
|
m_flInitializeTimestamp = Plat_FloatTime();
|
|
#if !defined( NO_STEAM )
|
|
SetupSteamRankingConfiguration();
|
|
#endif
|
|
}
|
|
if (
|
|
#if !defined( NO_STEAM )
|
|
( IsSteamRankingConfigured() || ( Plat_FloatTime() >= m_flInitializeTimestamp + mm_session_sys_ranking_timeout.GetFloat() ) ) &&
|
|
#endif
|
|
SysSession_AllowCreate()
|
|
)
|
|
UpdateStateInit();
|
|
break;
|
|
|
|
case STATE_CREATING:
|
|
break;
|
|
|
|
case STATE_REQUESTING_JOIN_DATA:
|
|
// Wait for 5 sec until data arrives
|
|
if ( Plat_FloatTime() > m_RequestJoinDataInfo.m_fTimeSent + mm_session_sys_connect_timeout.GetFloat() )
|
|
{
|
|
m_eState = STATE_FAIL;
|
|
|
|
#ifdef _X360
|
|
if ( m_pNetworkMgr )
|
|
{
|
|
m_pNetworkMgr->Destroy();
|
|
m_pNetworkMgr = NULL;
|
|
}
|
|
#endif
|
|
Warning( "CSysSessionClient: Unable to get session information from host\n" );
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "n/a" );
|
|
OnSessionEvent( kv );
|
|
}
|
|
break;
|
|
|
|
#if !defined( NO_STEAM )
|
|
case STATE_JOIN_LOBBY:
|
|
m_CallbackOnLobbyEntered.Register( this, &CSysSessionClient::Steam_OnLobbyEntered );
|
|
steamapicontext->SteamMatchmaking()->JoinLobby( m_lobby.m_uiLobbyID );
|
|
m_eState = STATE_CREATING;
|
|
break;
|
|
#endif
|
|
|
|
case STATE_IDLE:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSysSessionClient::Destroy()
|
|
{
|
|
// If we are migrating, then don't let the
|
|
// base class handle it because it will
|
|
// post Quit messages and leave the lobby...
|
|
if ( m_eState == STATE_MIGRATE )
|
|
{
|
|
delete this;
|
|
return;
|
|
}
|
|
|
|
#ifdef _X360
|
|
if ( m_eState == STATE_DELETE )
|
|
{
|
|
delete this;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_DELETE;
|
|
}
|
|
#endif
|
|
|
|
// Chain to base which will "delete this"
|
|
CSysSessionBase::Destroy();
|
|
}
|
|
|
|
void CSysSessionClient::DebugPrint()
|
|
{
|
|
DevMsg( "CSysSessionClient [ state=%d ]\n", m_eState );
|
|
|
|
if ( m_eState == STATE_REQUESTING_JOIN_DATA )
|
|
{
|
|
DevMsg( "Requested join data %.3f sec ago from xuid = %llx\n",
|
|
Plat_FloatTime() - m_RequestJoinDataInfo.m_fTimeSent, m_RequestJoinDataInfo.m_xuidLeader );
|
|
}
|
|
|
|
CSysSessionBase::DebugPrint();
|
|
}
|
|
|
|
XUID CSysSessionClient::GetHostXuid( XUID xuidValidResult )
|
|
{
|
|
#ifdef _X360
|
|
// Host is considered to be the first machine in our settings
|
|
// to which we have a network connection
|
|
int numMachines = m_pSettings->GetInt( "members/numMachines" );
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
|
|
if ( !pMachine )
|
|
continue;
|
|
|
|
XUID idMachine = pMachine->GetUint64( "id" );
|
|
if ( idMachine == m_xuidMachineId )
|
|
return m_xuidMachineId; // Reached our own machine, we are the top machine!
|
|
|
|
if ( xuidValidResult && idMachine == xuidValidResult )
|
|
return idMachine; // Maybe we don't have connection to the remote machine, but the caller thinks it could be a valid host
|
|
|
|
if ( m_pNetworkMgr->ConnectionPeerGetAddress( idMachine ) )
|
|
return idMachine;
|
|
}
|
|
// There's nobody in the session, maybe we are our own host?
|
|
return m_xuidMachineId;
|
|
#elif !defined( NO_STEAM )
|
|
return m_lobby.m_uiLobbyID ? steamapicontext->SteamMatchmaking()
|
|
->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64() : m_xuidMachineId;
|
|
#endif
|
|
return 0ull;
|
|
}
|
|
|
|
#ifdef _X360
|
|
char const * CSysSessionClient::GetHostNetworkAddress( XSESSION_INFO &xsi )
|
|
{
|
|
xsi = m_lobby.m_xiInfo;
|
|
char const *szNetworkAddress = m_pNetworkMgr->ConnectionPeerGetAddress( 0ull );
|
|
if ( !szNetworkAddress )
|
|
{
|
|
// Maybe migration hasn't finished yet...
|
|
XUID xuidHost = GetHostXuid();
|
|
|
|
DevWarning( "CSysSessionClient::GetHostNetworkAddress has no default host network address, retrying for %llx!\n", xuidHost );
|
|
szNetworkAddress = m_pNetworkMgr->ConnectionPeerGetAddress( xuidHost );
|
|
|
|
if ( !szNetworkAddress )
|
|
{
|
|
DevWarning( "CSysSessionClient::GetHostNetworkAddress has no host network address for %llx!\n", xuidHost );
|
|
Assert( 0 ); // this is fatal for our session and abnormal, but the UI should just pop a message that we failed to connect
|
|
}
|
|
}
|
|
return szNetworkAddress;
|
|
}
|
|
#endif
|
|
|
|
void CSysSessionClient::UpdateStateInit()
|
|
{
|
|
#ifdef _X360
|
|
|
|
MMX360_LobbyConnect( m_pSettings, &m_pAsyncOperation );
|
|
m_eState = STATE_CREATING;
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
m_lobby.m_uiLobbyID = m_pSettings->GetUint64( "options/sessionid", 0ull );
|
|
m_eState = STATE_JOIN_LOBBY;
|
|
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionClient::InitSessionProperties( KeyValues *pSettings )
|
|
{
|
|
#ifdef _X360
|
|
// Configure our session slots and permissions to match the host
|
|
CX360LobbyFlags_t fl = MMX360_DescribeLobbyFlags( pSettings, false );
|
|
g_pMatchExtensions->GetIXOnline()->XSessionModify( m_lobby.m_hHandle, fl.m_dwFlags, fl.m_numPublicSlots, fl.m_numPrivateSlots, MMX360_NewOverlappedDormant() );
|
|
|
|
// Join all the members
|
|
MMX360_LobbyJoinMembers( pSettings, m_lobby );
|
|
#endif
|
|
|
|
UpdateSessionProperties( pSettings );
|
|
}
|
|
|
|
void CSysSessionClient::UpdateSessionProperties( KeyValues *kv )
|
|
{
|
|
if ( !kv )
|
|
return;
|
|
|
|
CSysSessionBase::UpdateSessionProperties( kv, false );
|
|
|
|
//
|
|
// Set joinability and public/private slots distribution
|
|
//
|
|
|
|
#ifdef _X360
|
|
if ( !kv->FindKey( "members", false ) )
|
|
{
|
|
// If this is not our initial update
|
|
OnX360AllSessionMembersJoinLeave( kv );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
void CSysSessionClient::OnAsyncOperationFinished()
|
|
{
|
|
if ( m_eState == STATE_CREATING )
|
|
{
|
|
if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED )
|
|
{
|
|
Warning( "CSysSessionClient: CreateSession failed. Error %d\n", m_pAsyncOperation->GetResult() );
|
|
ReleaseAsyncOperation();
|
|
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "createclient" );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We have successfully created the client-side session,
|
|
// retrieve all information from the async creation.
|
|
//
|
|
m_lobby = m_pAsyncOperation->GetLobby();
|
|
ReleaseAsyncOperation();
|
|
|
|
// Initialize network manager
|
|
m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() );
|
|
if ( !m_pNetworkMgr->ConnectionPeerOpenActive( 0ull, m_lobby.m_xiInfo ) )
|
|
{
|
|
m_eState = STATE_FAIL;
|
|
|
|
m_pNetworkMgr->Destroy();
|
|
m_pNetworkMgr = NULL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "n/a" );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
|
|
// Request permission to join
|
|
m_eState = STATE_REQUESTING_JOIN_DATA;
|
|
m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime();
|
|
m_RequestJoinDataInfo.m_xuidLeader = 0ull;
|
|
|
|
Send_RequestJoinData();
|
|
return;
|
|
}
|
|
else if ( m_eState == STATE_DELETE )
|
|
{
|
|
ReleaseAsyncOperation();
|
|
SysSession360_RegisterPending( this );
|
|
}
|
|
else
|
|
{
|
|
ReleaseAsyncOperation();
|
|
}
|
|
}
|
|
|
|
void CSysSessionClient::OnX360NetDisconnected( XUID xuidRemote )
|
|
{
|
|
if ( m_eState == STATE_DELETE )
|
|
return;
|
|
|
|
// Do not react to disconnections among our peer clients,
|
|
// host is authoritative as far as which clients are in the session
|
|
// As soon as another client will migrate to be host that client
|
|
// will drop session members who haven't established the required
|
|
// P2P interconnect channels
|
|
if ( xuidRemote != GetHostXuid( xuidRemote ) ) // Indicate that the disconnected XUID could have been the host
|
|
{
|
|
DevMsg( "CSysSessionClient::OnX360NetDisconnected( %llx ) waiting for host update.\n", xuidRemote );
|
|
return;
|
|
}
|
|
|
|
CSysSessionBase::OnX360NetDisconnected( xuidRemote );
|
|
}
|
|
|
|
bool CSysSessionClient::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg )
|
|
{
|
|
if ( m_eState == STATE_IDLE && !Q_stricmp( msg->GetName(), "SysSession::P2PConnect" ) )
|
|
{
|
|
// Check nonce the client is trying to connect to
|
|
uint64 uiNonce = msg->GetUint64( "nonce" );
|
|
if ( uiNonce != ( const uint64 & ) m_lobby.m_uiNonce )
|
|
return false;
|
|
|
|
// This is a legit connectionless packet requesting a p2p connection
|
|
XUID machineid = msg->GetUint64( "id" );
|
|
XNKID xnkidSession = m_lobby.m_xiInfo.sessionID;
|
|
|
|
// Pick up the peer and add it to our spider web of connections
|
|
if ( !m_pNetworkMgr->ConnectionPeerOpenPassive( machineid, pkt, &xnkidSession ) )
|
|
{
|
|
DevWarning( "CSysSessionClient::P2PConnect failed to open passive connection with %llx!\n", machineid );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Unknown packet, permanently block sender
|
|
return false;
|
|
}
|
|
|
|
void CSysSessionClient::XP2P_Interconnect()
|
|
{
|
|
// Peer-to-peer connection establish packet
|
|
KeyValues *p2pMsg = new KeyValues( "SysSession::P2PConnect" );
|
|
KeyValues::AutoDelete autodelete_p2pMsg( p2pMsg );
|
|
p2pMsg->SetUint64( "nonce", m_lobby.m_uiNonce );
|
|
p2pMsg->SetUint64( "id", m_xuidMachineId );
|
|
|
|
// Open an active connection to all current peers
|
|
if ( KeyValues *kvMembers = m_pSettings->FindKey( "members" ) )
|
|
{
|
|
int numMachines = kvMembers->GetInt( "numMachines" );
|
|
for ( int k = 1; k < numMachines; ++ k ) // skip "0" because it's the host
|
|
{
|
|
KeyValues *kvMachine = kvMembers->FindKey( CFmtStr( "machine%d", k ) );
|
|
if ( !kvMachine )
|
|
continue;
|
|
|
|
XSESSION_INFO xsi = m_lobby.m_xiInfo;
|
|
MMX360_XnaddrFromString( xsi.hostAddress, kvMachine->GetString( "xnaddr" ) );
|
|
if ( !memcmp( &xsi.hostAddress, &m_xnaddrLocal, sizeof( m_xnaddrLocal ) ) )
|
|
continue;
|
|
|
|
XUID idMachine = kvMachine->GetUint64( "id" );
|
|
if ( m_pNetworkMgr->ConnectionPeerOpenActive( idMachine, xsi ) )
|
|
{
|
|
m_pNetworkMgr->ConnectionPeerSendConnectionless( idMachine, p2pMsg );
|
|
}
|
|
else
|
|
{
|
|
DevWarning( "CSysSessionClient::XP2P_Interconnect failed to interconnect with %llx!\n", idMachine );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void CSysSessionClient::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
|
|
{
|
|
char const *szMsg = msg->GetName();
|
|
|
|
// Parse the message depending on our state
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_REQUESTING_JOIN_DATA:
|
|
if ( !Q_stricmp( szMsg, "SysSession::ReplyJoinData" ) )
|
|
{
|
|
if ( ( msg->GetUint64( "id" ) == m_xuidMachineId )
|
|
&& ( xuidSrc == GetHostXuid() ) )
|
|
{
|
|
Process_ReplyJoinData_Our( msg );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_IDLE:
|
|
if ( !bValidatedLobbyMember )
|
|
{
|
|
return;
|
|
}
|
|
else if ( !Q_stricmp( szMsg, "SysSession::ReplyJoinData" ) )
|
|
{
|
|
if ( GetHostXuid() == xuidSrc )
|
|
Process_ReplyJoinData_Other( msg );
|
|
}
|
|
else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerRemoved" ) )
|
|
{
|
|
if ( GetHostXuid() == xuidSrc )
|
|
FindAndRemovePlayerFromMembers( msg->GetUint64( "xuid" ) );
|
|
}
|
|
else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerKicked" ) )
|
|
{
|
|
if ( GetHostXuid() == xuidSrc )
|
|
{
|
|
XUID xuid = msg->GetUint64( "xuid" );
|
|
if ( xuid == m_xuidMachineId )
|
|
Process_Kicked( msg );
|
|
else
|
|
FindAndRemovePlayerFromMembers( xuid );
|
|
}
|
|
}
|
|
else if ( !Q_stricmp( szMsg, "SysSession::OnPlayerUpdated" ) )
|
|
{
|
|
if ( GetHostXuid() == xuidSrc )
|
|
Process_OnPlayerUpdated( msg );
|
|
}
|
|
else if ( !Q_stricmp( szMsg, "SysSession::OnMachineUpdated" ) )
|
|
{
|
|
if ( GetHostXuid() == xuidSrc )
|
|
Process_OnMachineUpdated( msg );
|
|
}
|
|
#ifdef _X360
|
|
else if ( !Q_stricmp( szMsg, "SysSession::HostMigrated" ) )
|
|
{
|
|
XSESSION_INFO xsi;
|
|
char const *chSessionInfo = msg->GetString( "sessioninfo", "" );
|
|
MMX360_SessionInfoFromString( xsi, chSessionInfo );
|
|
|
|
// If there is an outstanding migrate call, then disable our listener on it
|
|
if ( m_hLobbyMigrateCall && !m_MigrateCallState.m_bFinished )
|
|
MMX360_LobbyMigrateSetListener( m_hLobbyMigrateCall, NULL );
|
|
m_hLobbyMigrateCall = NULL;
|
|
|
|
// Schedule a client migrate call
|
|
m_hLobbyMigrateCall = MMX360_LobbyMigrateClient( m_lobby, xsi, &m_MigrateCallState );
|
|
|
|
if ( !m_hLobbyMigrateCall )
|
|
{
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "migrate" );
|
|
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Update our network mgr to reflect the new host
|
|
XUID id = msg->GetUint64( "id" );
|
|
m_pNetworkMgr->ConnectionPeerUpdateXuid( id, 0ull );
|
|
|
|
DevMsg( "CSysSessionClient - client migration scheduled - %s\n", chSessionInfo );
|
|
}
|
|
|
|
// Now purge all network connections that the host is no longer supporting
|
|
if ( int numMachines = m_pSettings->GetInt( "members/numMachines" ) )
|
|
{
|
|
CUtlVector< XUID > arrCloseConnections;
|
|
for ( int k = 0; k < numMachines; ++ k )
|
|
{
|
|
KeyValues *pMachine = m_pSettings->FindKey( CFmtStr( "members/machine%d", k ) );
|
|
if ( !pMachine )
|
|
continue;
|
|
|
|
XUID idMachine = pMachine->GetUint64( "id" );
|
|
if ( !msg->GetString( CFmtStr( "machines/%llx", idMachine ), NULL ) )
|
|
arrCloseConnections.AddToTail( idMachine );
|
|
}
|
|
for ( int k = 0; k < arrCloseConnections.Count(); ++ k )
|
|
{
|
|
XUID idMachine = arrCloseConnections[k];
|
|
m_pNetworkMgr->ConnectionPeerClose( idMachine );
|
|
OnPlayerLeave( idMachine );
|
|
}
|
|
}
|
|
|
|
// Send a notification
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
|
|
kvEvent->SetString( "state", "client" );
|
|
kvEvent->SetUint64( "xuid", msg->GetUint64( "id" ) );
|
|
kvEvent->SetString( "migration", "started" );
|
|
SendEventsNotification( kvEvent );
|
|
}
|
|
#endif
|
|
else if ( !Q_stricmp( szMsg, "SysSession::OnUpdate" ) )
|
|
{
|
|
if ( GetHostXuid() == xuidSrc )
|
|
{
|
|
// Host is making changes to the session
|
|
m_pSettings->MergeFrom( msg );
|
|
|
|
// Take care of the updated properties on the client side
|
|
UpdateSessionProperties( msg->FindKey( "update", false ) );
|
|
|
|
// Broadcast the update to everybody interested
|
|
MatchSession_BroadcastSessionSettingsUpdate( msg );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef _X360
|
|
#elif !defined( NO_STEAM )
|
|
void CSysSessionClient::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter )
|
|
{
|
|
// Filter out notifications not from our lobby
|
|
if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
m_CallbackOnLobbyEntered.Unregister();
|
|
|
|
if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
|
|
{
|
|
Warning( "CSysSessionClient: Cannot join lobby, response %d!\n", pLobbyEnter->m_EChatRoomEnterResponse );
|
|
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", LobbyEnterErrorAsString( pLobbyEnter ) );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
XUID xuidLeader = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64();
|
|
if ( m_xuidMachineId == xuidLeader )
|
|
{
|
|
Warning( "CSysSessionClient: Host left lobby, unable to migrate\n" );
|
|
|
|
// We entered the lobby, but the host left
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "n/a" );
|
|
OnSessionEvent( kv );
|
|
return;
|
|
}
|
|
|
|
// DBUGG
|
|
// What lobby have we just joined
|
|
{
|
|
DevMsg( "Joined lobby %llx\n", m_lobby.m_uiLobbyID );
|
|
int i, dataCount;
|
|
dataCount = steamapicontext->SteamMatchmaking()->GetLobbyDataCount( m_lobby.m_uiLobbyID );
|
|
for ( i=0; i < dataCount; i++ )
|
|
{
|
|
char key[64];
|
|
char val[64];
|
|
steamapicontext->SteamMatchmaking()->GetLobbyDataByIndex( m_lobby.m_uiLobbyID,
|
|
i, key, sizeof(key), val, sizeof( val ));
|
|
DevMsg( "Lobby data: %s = %s\n", key, val );
|
|
}
|
|
}
|
|
|
|
// In the case that the player is joining via direct connect we only want to proceed if
|
|
// 1. The player has a friend in the lobby
|
|
// 2. The player is not blocked by anyone in the lobby
|
|
// If we have got this far (2) has already been taken care of by the frenemies code
|
|
// so we just need to take care of (1)
|
|
// The "options/dcFriendsReqd" flag is set in the client-server protocol
|
|
// For direct connects this handshake has already happened before the lobby is joined
|
|
// For matchmaking this happens after the lobby is joined so is irrelevant
|
|
KeyValuesDumpAsDevMsg( m_pSettings );
|
|
bool bFriendsReqd = m_pSettings->GetBool( "options/dcFriendsRed", false );
|
|
if ( bFriendsReqd )
|
|
{
|
|
// Check if the player is joining via direct connect (or invite)
|
|
// if joining via invite the player will pass the test below
|
|
const char *action = m_pSettings->GetString( "options/action", "" );
|
|
if ( !Q_stricmp( action, "joinsession" ) )
|
|
{
|
|
// Walk lobby members
|
|
int i, numLobbyMembers = steamapicontext->SteamMatchmaking()->GetNumLobbyMembers( m_lobby.m_uiLobbyID );
|
|
CSteamID playerID = steamapicontext->SteamUser()->GetSteamID();
|
|
|
|
for ( i = 0; i < numLobbyMembers; i++ )
|
|
{
|
|
CSteamID memberId = steamapicontext->SteamMatchmaking()->GetLobbyMemberByIndex(
|
|
m_lobby.m_uiLobbyID, i );
|
|
|
|
if ( memberId == playerID )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
EFriendRelationship reln = steamapicontext->SteamFriends()->GetFriendRelationship( memberId );
|
|
|
|
if ( reln == k_EFriendRelationshipFriend )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i >= numLobbyMembers )
|
|
{
|
|
// No friends found - leave lobby
|
|
steamapicontext->SteamMatchmaking()->LeaveLobby( m_lobby.m_uiLobbyID );
|
|
|
|
Warning( "CSysSessionClient: No friends in lobby - cannot join\n");
|
|
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "Friend reqd to join lobby" );
|
|
OnSessionEvent( kv );
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request permission to join
|
|
m_eState = STATE_REQUESTING_JOIN_DATA;
|
|
m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime();
|
|
m_RequestJoinDataInfo.m_xuidLeader = xuidLeader;
|
|
|
|
Send_RequestJoinData();
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CSysSessionClient::Process_ReplyJoinData_Our( KeyValues *msg )
|
|
{
|
|
DevMsg("CSysSessionClient::Process_ReplyJoinData_Our\n");
|
|
KeyValuesDumpAsDevMsg( msg );
|
|
|
|
KeyValues *pSettings = msg->FindKey( "settings" );
|
|
char const *szError = msg->GetString( "error", NULL );
|
|
if ( !pSettings || szError )
|
|
{
|
|
Warning( "CSysSessionClient: Received bad session data from host\n" );
|
|
m_eState = STATE_FAIL;
|
|
|
|
KeyValues *kv = msg->MakeCopy();
|
|
kv->SetName( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
if ( !szError )
|
|
kv->SetString( "error", "n/a" );
|
|
OnSessionEvent( kv );
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_IDLE;
|
|
|
|
#ifdef _X360
|
|
uint64 uiNonce = pSettings->GetUint64( "system/nonce" );
|
|
m_lobby.m_uiNonce = uiNonce;
|
|
|
|
// Update host connection XUID
|
|
if ( XUID xuidHost = pSettings->GetUint64( "members/machine0/id", 0ull ) )
|
|
{
|
|
m_pNetworkMgr->ConnectionPeerUpdateXuid( 0ull, xuidHost );
|
|
}
|
|
#endif
|
|
|
|
// We have received an entirely new "settings" data, copy that to our "settings" data
|
|
// after saving settings we need to restore
|
|
int conteam = m_pSettings->GetInt( "conteam", -1 );
|
|
|
|
m_pSettings->Clear();
|
|
m_pSettings->SetName( pSettings->GetName() );
|
|
m_pSettings->MergeFrom( pSettings, KeyValues::MERGE_KV_UPDATE );
|
|
|
|
InitSessionProperties( m_pSettings );
|
|
|
|
if ( conteam != -1 )
|
|
{
|
|
m_pSettings->SetInt("conteam", conteam );
|
|
}
|
|
|
|
// Setup voice engine
|
|
Voice_ProcessTalkers( NULL, true );
|
|
Voice_UpdateMutelist();
|
|
|
|
#ifdef _X360
|
|
// Peer-to-peer interconnect
|
|
XP2P_Interconnect();
|
|
#endif
|
|
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
if ( uint64 ullCrypt = msg->GetUint64( "crypt" ) )
|
|
kv->SetUint64( "crypt", ullCrypt );
|
|
|
|
OnSessionEvent( kv );
|
|
}
|
|
}
|
|
|
|
void CSysSessionClient::Process_ReplyJoinData_Other( KeyValues *msg )
|
|
{
|
|
// Somebody has joined the session
|
|
char const *szError = msg->GetString( "error", NULL );
|
|
KeyValues *pSettings = msg->FindKey( "settings" );
|
|
if ( !pSettings || szError )
|
|
// connection attempt was rejected
|
|
return;
|
|
|
|
KeyValues *pMembers = pSettings->FindKey( "members" );
|
|
if ( !pMembers )
|
|
return;
|
|
|
|
// Now process all the new machines to notify the subscribers of
|
|
// new players
|
|
int numMachinesOld = m_pSettings->GetInt( "members/numMachines", 0 );
|
|
|
|
// Use this opportunity to fully sync-up our client-side settings
|
|
int conteam = m_pSettings->GetInt( "conteam", -1 );
|
|
m_pSettings->Clear();
|
|
m_pSettings->SetName( pSettings->GetName() );
|
|
m_pSettings->MergeFrom( pSettings, KeyValues::MERGE_KV_UPDATE );
|
|
if ( conteam != -1 )
|
|
{
|
|
m_pSettings->SetInt("conteam", conteam );
|
|
}
|
|
|
|
#ifdef _X360
|
|
// On X360 we need to update lobby members server-side count
|
|
MMX360_LobbyJoinMembers( pSettings, m_lobby, numMachinesOld );
|
|
#endif
|
|
|
|
// Run over all new machines
|
|
int numMachinesNew = m_pSettings->GetInt( "members/numMachines", 0 );
|
|
Assert( numMachinesNew > numMachinesOld );
|
|
for ( int k = numMachinesOld; k < numMachinesNew; ++ k )
|
|
{
|
|
KeyValues *kvMachine = pMembers->FindKey( CFmtStr( "machine%d", k ) );
|
|
if ( !kvMachine )
|
|
continue;
|
|
|
|
// Register talkers
|
|
Voice_ProcessTalkers( kvMachine, true );
|
|
|
|
int numPlayers = kvMachine->GetInt( "numPlayers", 0 );
|
|
for ( int j = 0; j < numPlayers; ++ j )
|
|
{
|
|
KeyValues *pPlayer = kvMachine->FindKey( CFmtStr( "player%d", j ) );
|
|
XUID xuid = pPlayer->GetUint64( "xuid", 0ull );
|
|
if ( !xuid )
|
|
continue;
|
|
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
|
|
kvEvent->SetString( "state", "joined" );
|
|
kvEvent->SetUint64( "xuid", xuid );
|
|
kvEvent->SetPtr( "player", pPlayer );
|
|
OnSessionEvent( kvEvent );
|
|
}
|
|
}
|
|
|
|
Voice_UpdateMutelist();
|
|
}
|
|
|
|
void CSysSessionClient::Process_OnPlayerUpdated( KeyValues *msg )
|
|
{
|
|
KeyValues *pPlayer = SessionMembersFindPlayer( m_pSettings, msg->GetUint64( "xuid" ) );
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
char chNameBuffer[64];
|
|
Q_snprintf( chNameBuffer, ARRAYSIZE( chNameBuffer ), "%s", pPlayer->GetName() );
|
|
|
|
pPlayer->Clear();
|
|
pPlayer->SetName( chNameBuffer );
|
|
pPlayer->MergeFrom( msg, KeyValues::MERGE_KV_UPDATE );
|
|
|
|
// Notify the framework about player updated
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerUpdated" );
|
|
kvEvent->SetUint64( "xuid", pPlayer->GetUint64( "xuid" ) );
|
|
kvEvent->SetPtr( "player", pPlayer );
|
|
OnSessionEvent( kvEvent );
|
|
}
|
|
|
|
void CSysSessionClient::Process_OnMachineUpdated( KeyValues *msg )
|
|
{
|
|
KeyValues *pMachine = NULL;
|
|
SessionMembersFindPlayer( m_pSettings, msg->GetUint64( "id" ), &pMachine );
|
|
if ( !pMachine )
|
|
return;
|
|
|
|
char chNameBuffer[64];
|
|
Q_snprintf( chNameBuffer, ARRAYSIZE( chNameBuffer ), "%s", pMachine->GetName() );
|
|
|
|
pMachine->Clear();
|
|
pMachine->SetName( chNameBuffer );
|
|
pMachine->MergeFrom( msg, KeyValues::MERGE_KV_UPDATE );
|
|
}
|
|
|
|
void CSysSessionClient::Process_Kicked( KeyValues *msg )
|
|
{
|
|
m_eState = STATE_FAIL;
|
|
|
|
// Prepare the update notification
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
kv->SetString( "error", "kicked" );
|
|
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
}
|
|
|
|
void CSysSessionClient::Send_RequestJoinData()
|
|
{
|
|
KeyValues *msg = new KeyValues( "SysSession::RequestJoinData" );
|
|
KeyValues::AutoDelete autodelete( msg );
|
|
|
|
msg->SetUint64( "id", m_xuidMachineId );
|
|
|
|
// msg->AddSubKey( m_pSettings->MakeCopy() ); << don't send all settings, there's some unnecessary data
|
|
msg->FindKey( "settings", true )->AddSubKey(m_pSettings->FindKey( "members" )->MakeCopy() );
|
|
|
|
KeyValues *pSystem = m_pSettings->FindKey( "options" );
|
|
uint64 teamResKey = pSystem->GetUint64( "teamResKey", 0 );
|
|
|
|
msg->FindKey( "settings" )->SetUint64( "teamResKey", teamResKey );
|
|
|
|
if ( mm_session_sys_pkey.GetString()[0] )
|
|
{
|
|
msg->SetString( "settings/system/lock", mm_session_sys_pkey.GetString() );
|
|
}
|
|
|
|
// If client needs to do join checks then forward them to host
|
|
if ( KeyValues *kvJoinCheck = m_pSettings->FindKey( "joincheck" ) )
|
|
msg->FindKey( "settings" )->AddSubKey( kvJoinCheck->MakeCopy() );
|
|
|
|
DevMsg( "Sending join session request...\n" );
|
|
KeyValuesDumpAsDevMsg( msg, 1 );
|
|
|
|
#ifdef _X360
|
|
|
|
// Set the session id that we are connecting to
|
|
msg->SetUint64( "sessionid", ( const uint64 & ) m_lobby.m_xiInfo.sessionID );
|
|
|
|
// Resolve this machine's XNADDR
|
|
memset( &m_xnaddrLocal, 0, sizeof( m_xnaddrLocal ) );
|
|
while( XNET_GET_XNADDR_PENDING == g_pMatchExtensions->GetIXOnline()->XNetGetTitleXnAddr( &m_xnaddrLocal ) )
|
|
continue;
|
|
|
|
char chXnaddr[ XNADDR_STRING_LENGTH ] = {0};
|
|
MMX360_XnaddrToString( m_xnaddrLocal, chXnaddr );
|
|
|
|
msg->SetString( "settings/members/machine0/xnaddr", chXnaddr );
|
|
|
|
// Now contact the remote host
|
|
m_pNetworkMgr->ConnectionPeerSendConnectionless( 0ull, msg );
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
// Install callback for messages
|
|
m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
|
|
m_CallbackOnLobbyChatUpdate.Register( this, &CSysSessionBase::Steam_OnLobbyChatUpdate );
|
|
SendMessage( msg );
|
|
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionClient::Migrate( KeyValues *pCommand )
|
|
{
|
|
if ( Q_stricmp( pCommand->GetString( "migrate" ), "client>host" ) )
|
|
{
|
|
Assert( 0 );
|
|
return;
|
|
}
|
|
|
|
#ifndef NO_STEAM
|
|
uint64 uiNewLobbyOwner = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64();
|
|
Assert( uiNewLobbyOwner == m_xuidMachineId );
|
|
uiNewLobbyOwner;
|
|
|
|
// Prepare the update notification
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
m_eState = STATE_MIGRATE;
|
|
kv->SetString( "action", "host" );
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
#endif
|
|
}
|
|
|
|
void CSysSessionClient::OnPlayerLeave( XUID xuid )
|
|
{
|
|
#if !defined( NO_STEAM )
|
|
if ( !V_stricmp( m_pSettings->GetString( "system/netflag" ), "noleave" ) )
|
|
{
|
|
DevMsg( "CSysSessionClient::OnPlayerLeave(%llx) ignored in noleave mode.\n", xuid );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
XUID xuidCurrentHost = GetHostXuid( xuid ); // Indicate the the leaving XUID could have been the host
|
|
|
|
if ( m_eState == STATE_IDLE || m_eState == STATE_MIGRATE )
|
|
{
|
|
FindAndRemovePlayerFromMembers( xuid );
|
|
}
|
|
|
|
// We only care to handle this event further if we are becoming the new host
|
|
char const *szForcedError = NULL;
|
|
|
|
#ifdef _X360
|
|
|
|
XUID xuidNewHost = GetHostXuid();
|
|
|
|
if ( m_eState == STATE_REQUESTING_JOIN_DATA )
|
|
{
|
|
szForcedError = "n/a";
|
|
}
|
|
else if ( xuidCurrentHost != xuid )
|
|
{
|
|
// Current host is not leaving, so we are fine
|
|
return;
|
|
}
|
|
else if ( xuidNewHost != m_xuidMachineId )
|
|
{
|
|
// The host is leaving, but we don't plan to host
|
|
DevMsg( "CSysSessionClient::OnPlayerLeave - host left, waiting for new host (%llx)...\n", xuidNewHost );
|
|
|
|
// Send a notification
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
|
|
kvEvent->SetString( "state", "client" );
|
|
kvEvent->SetUint64( "xuid", xuidNewHost );
|
|
kvEvent->SetString( "migration", "waiting" );
|
|
SendEventsNotification( kvEvent );
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "CSysSessionClient::OnPlayerLeave - becoming new host!\n" );
|
|
}
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
XUID xuidNewHost = steamapicontext->SteamMatchmaking()->GetLobbyOwner( m_lobby.m_uiLobbyID ).ConvertToUint64();
|
|
if ( xuidNewHost != m_xuidMachineId )
|
|
{
|
|
if ( m_eState == STATE_REQUESTING_JOIN_DATA &&
|
|
xuid == m_RequestJoinDataInfo.m_xuidLeader )
|
|
{
|
|
// Resend join request
|
|
// m_RequestJoinDataInfo.m_fTimeSent = Plat_FloatTime();
|
|
m_RequestJoinDataInfo.m_xuidLeader = xuidNewHost;
|
|
|
|
Send_RequestJoinData();
|
|
}
|
|
else if ( xuidNewHost != xuidCurrentHost )
|
|
{
|
|
// Send a notification
|
|
KeyValues *kvEvent = new KeyValues( "OnPlayerLeaderChanged" );
|
|
kvEvent->SetString( "state", "client" );
|
|
kvEvent->SetUint64( "xuid", xuidNewHost );
|
|
SendEventsNotification( kvEvent );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( m_eState != STATE_IDLE )
|
|
szForcedError = "n/a";
|
|
|
|
#endif
|
|
|
|
// Prepare the update notification
|
|
KeyValues *kv = new KeyValues( "mmF->SysSessionUpdate" );
|
|
kv->SetPtr( "syssession", this );
|
|
|
|
// If migration is not possible at this stage,
|
|
// then a forced error will send us into FAIL state
|
|
if ( szForcedError )
|
|
{
|
|
m_eState = STATE_FAIL;
|
|
|
|
kv->SetString( "error", szForcedError );
|
|
}
|
|
else if ( !Q_stricmp( "teamlink", m_pSettings->GetString( "system/netflag", "" ) ) )
|
|
{
|
|
// Team link sessions cannot migrate, we just trigger an error so that
|
|
// entire client link session including network manager could destroy properly
|
|
m_eState = STATE_FAIL;
|
|
|
|
kv->SetString( "error", "migrate" );
|
|
}
|
|
else
|
|
{
|
|
// We are about to migrate and become the new host
|
|
// See if the title settings mgr wants to chime in
|
|
if ( KeyValues *pMigrationHdlr = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareClientLobbyForMigration( m_pSettings, NULL ) )
|
|
{
|
|
KeyValues::AutoDelete autodelete( pMigrationHdlr );
|
|
if ( char const *szError = pMigrationHdlr->GetString( "error", NULL ) )
|
|
{
|
|
// Title forcing a migration error
|
|
m_eState = STATE_FAIL;
|
|
kv->SetString( "error", szError );
|
|
|
|
// Enqueue disconnect command
|
|
g_pMatchExtensions->GetIVEngineClient()->ClientCmd( "disconnect" );
|
|
}
|
|
}
|
|
|
|
if ( m_eState != STATE_FAIL )
|
|
{
|
|
m_eState = STATE_MIGRATE;
|
|
|
|
kv->SetString( "action", "host" );
|
|
}
|
|
}
|
|
|
|
// Inside this event broadcast our session will be deleted
|
|
SendEventsNotification( kv );
|
|
}
|
|
|
|
|
|
CSysSessionConTeamHost::CSysSessionConTeamHost( KeyValues *pSettings ) :
|
|
CSysSessionBase( pSettings ),
|
|
m_eState( STATE_INIT ),
|
|
m_lastRequestSendTime( 0.0f )
|
|
{
|
|
#if !defined (NO_STEAM)
|
|
m_CallbackOnLobbyChatMsg.Register( this, &CSysSessionBase::Steam_OnLobbyChatMsg );
|
|
#endif
|
|
}
|
|
|
|
CSysSessionConTeamHost::~CSysSessionConTeamHost()
|
|
{
|
|
#if !defined (NO_STEAM)
|
|
m_CallbackOnLobbyChatMsg.Unregister();
|
|
#endif
|
|
}
|
|
|
|
bool CSysSessionConTeamHost::Update()
|
|
{
|
|
if (!CSysSessionBase::Update())
|
|
return false;
|
|
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_INIT:
|
|
|
|
#if defined (_X360)
|
|
|
|
Succeeded();
|
|
break;
|
|
|
|
// MMX360_LobbyConnect( m_pSettings, &m_pAsyncOperation );
|
|
|
|
#elif !defined (NO_STEAM)
|
|
|
|
// Join lobby
|
|
m_lobby.m_uiLobbyID = m_pSettings->GetUint64( "options/sessionid", 0ull );
|
|
m_CallbackOnLobbyEntered.Register( this, &CSysSessionConTeamHost::Steam_OnLobbyEntered );
|
|
steamapicontext->SteamMatchmaking()->JoinLobby( m_lobby.m_uiLobbyID );
|
|
|
|
#endif
|
|
|
|
m_eState = STATE_WAITING_LOBBY_JOIN;
|
|
break;
|
|
|
|
case STATE_SEND_RESERVATION_REQUEST:
|
|
SendReservationRequest();
|
|
m_eState = STATE_WAITING_RESERVATION_REQUEST;
|
|
break;
|
|
|
|
case STATE_WAITING_RESERVATION_REQUEST:
|
|
|
|
if ( Plat_FloatTime() > m_lastRequestSendTime + mm_session_sys_connect_timeout.GetFloat() )
|
|
{
|
|
DevMsg( "CSysSessionConTeamHost: Timed out waiting for reservation reply\n");
|
|
Failed();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSysSessionConTeamHost::Destroy()
|
|
{
|
|
#ifdef _X360
|
|
if ( m_eState == STATE_DELETE )
|
|
{
|
|
delete this;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_DELETE;
|
|
}
|
|
#endif
|
|
|
|
// Chain to base which will "delete this"
|
|
CSysSessionBase::Destroy();
|
|
}
|
|
|
|
bool CSysSessionConTeamHost::GetPlayerSidesAssignment( int *numPlayers, uint64 playerIDs[10], int side[10] )
|
|
{
|
|
#if !defined (NO_STEAM)
|
|
if ( GetResult() != RESULT_SUCCESS )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char* szNumTSlotsFree = steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numTSlotsFree" );
|
|
const char* szNumCTSlotsFree = steamapicontext->SteamMatchmaking()->GetLobbyData( m_lobby.m_uiLobbyID, "members:numCTSlotsFree" );
|
|
int numTeamPlayers = m_pSettings->GetInt( "members/numPlayers" );
|
|
|
|
DevMsg( "CSysSessionConTeamHost: numTSlotsFree = %s, numCTSlotsFree = %s\n", szNumTSlotsFree, szNumCTSlotsFree);
|
|
|
|
// Choose a team to join
|
|
int numSlotsFree[2];
|
|
|
|
numSlotsFree[0] = atoi( szNumCTSlotsFree );
|
|
numSlotsFree[1] = atoi( szNumTSlotsFree );
|
|
|
|
// Choose a team at random
|
|
int team = RandomInt(0, 1);
|
|
int otherTeam = 1 - team;
|
|
|
|
// If we chose a team with too few slots, but other team has enough
|
|
// then swap
|
|
if ( numSlotsFree[team] < numTeamPlayers )
|
|
{
|
|
if ( numSlotsFree[otherTeam] >= numTeamPlayers )
|
|
{
|
|
team = otherTeam;
|
|
otherTeam = 1 - team;
|
|
}
|
|
}
|
|
|
|
KeyValues *pMembers = m_pSettings->FindKey( "members" );
|
|
|
|
// Assign players to different teams
|
|
for ( int i = 0; i < numTeamPlayers; i++ )
|
|
{
|
|
const char *playerKey = CFmtStr( "machine%d/player0", i );
|
|
KeyValues *pPlayer = pMembers->FindKey( playerKey );
|
|
uint64 playerId = pPlayer->GetUint64( "xuid", 0 );
|
|
|
|
playerIDs[i] = playerId;
|
|
|
|
// In keyvalues, team CT = 1, team T = 2
|
|
if ( numSlotsFree[team] )
|
|
{
|
|
side[i] = team + 1;
|
|
numSlotsFree[team]--;
|
|
}
|
|
else
|
|
{
|
|
side[i] = otherTeam + 1;
|
|
}
|
|
}
|
|
|
|
*numPlayers = numTeamPlayers;
|
|
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSysSessionConTeamHost::ReceiveMessage( KeyValues *msg, bool bValidatedLobbyMember, XUID xuidSrc )
|
|
{
|
|
#if !defined (NO_STEAM)
|
|
|
|
char const *szMsg = msg->GetName();
|
|
bool bProcessed = false;
|
|
|
|
switch ( m_eState )
|
|
{
|
|
case STATE_WAITING_RESERVATION_REQUEST:
|
|
|
|
if ( !Q_stricmp( "SysSession::TeamReservationResult", szMsg ) )
|
|
{
|
|
bProcessed = true;
|
|
|
|
const char *res = msg->GetString( "result");
|
|
DevMsg( "CSysSessionConTeamHost: TeamReservationResult = %s\n", res );
|
|
|
|
if ( !Q_stricmp( "success", res ) )
|
|
{
|
|
Succeeded();
|
|
}
|
|
else
|
|
{
|
|
Failed();
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if ( !bProcessed )
|
|
{
|
|
CSysSessionBase::ReceiveMessage( msg, bValidatedLobbyMember, xuidSrc );
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
void CSysSessionConTeamHost::SendReservationRequest()
|
|
{
|
|
// Send reservation chat msg
|
|
KeyValues *reservation = new KeyValues( "SysSession::TeamReservation" );
|
|
KeyValues::AutoDelete autodelete( reservation );
|
|
|
|
int numTeamPlayers = m_pSettings->GetInt( "members/numPlayers" );
|
|
reservation->SetInt( "numPlayers", numTeamPlayers );
|
|
|
|
uint64 teamResKey = m_pSettings->GetUint64( "members/machine0/id", 0 );
|
|
reservation->SetUint64( "teamResKey", teamResKey );
|
|
|
|
DevMsg( "Sending res request with teamResKey == %llx\n ", teamResKey );
|
|
|
|
#if defined (_X360)
|
|
|
|
// Now contact the remote host
|
|
m_pNetworkMgr->ConnectionPeerSendConnectionless( 0ull, reservation );
|
|
|
|
#elif !defined (NO_STEAM)
|
|
|
|
|
|
CUtlBuffer buf;
|
|
buf.ActivateByteSwapping( !CByteswap::IsMachineBigEndian() );
|
|
buf.PutInt( g_pMatchExtensions->GetINetSupport()->GetEngineBuildNumber() );
|
|
reservation->WriteAsBinary( buf );
|
|
|
|
steamapicontext->SteamMatchmaking()->SendLobbyChatMsg( m_lobby.m_uiLobbyID, buf.Base(), buf.TellMaxPut() );
|
|
|
|
#endif
|
|
|
|
m_lastRequestSendTime = Plat_FloatTime();
|
|
}
|
|
|
|
|
|
void CSysSessionConTeamHost::Succeeded()
|
|
{
|
|
m_eState = STATE_DONE;
|
|
m_result = RESULT_SUCCESS;
|
|
}
|
|
|
|
void CSysSessionConTeamHost::Failed()
|
|
{
|
|
m_eState = STATE_DONE;
|
|
m_result = RESULT_FAIL;
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
void CSysSessionConTeamHost::OnAsyncOperationFinished()
|
|
{
|
|
if (m_eState == STATE_WAITING_LOBBY_JOIN )
|
|
{
|
|
if ( m_pAsyncOperation->GetState() != AOS_SUCCEEDED )
|
|
{
|
|
Warning( "CSysSessionConTeamHost: Could not join lobby\n" );
|
|
ReleaseAsyncOperation();
|
|
Failed();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We have successfully created the client-side session,
|
|
// retrieve all information from the async creation.
|
|
//
|
|
m_lobby = m_pAsyncOperation->GetLobby();
|
|
ReleaseAsyncOperation();
|
|
|
|
// Initialize network manager
|
|
m_pNetworkMgr = new CX360NetworkMgr( this, GetX360NetSocket() );
|
|
if ( !m_pNetworkMgr->ConnectionPeerOpenActive( 0ull, m_lobby.m_xiInfo ) )
|
|
{
|
|
m_pNetworkMgr->Destroy();
|
|
m_pNetworkMgr = NULL;
|
|
|
|
Warning( "CSysSessionConTeamHost: ConnectionPeerOpenActive failed\n" );
|
|
Failed();
|
|
return;
|
|
}
|
|
|
|
m_eState = STATE_SEND_RESERVATION_REQUEST;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ReleaseAsyncOperation();
|
|
}
|
|
}
|
|
|
|
#elif !defined( NO_STEAM )
|
|
|
|
void CSysSessionConTeamHost::Steam_OnLobbyEntered( LobbyEnter_t *pLobbyEnter )
|
|
{
|
|
// Filter out notifications not from our lobby
|
|
if ( pLobbyEnter->m_ulSteamIDLobby != m_lobby.m_uiLobbyID )
|
|
return;
|
|
|
|
m_CallbackOnLobbyEntered.Unregister();
|
|
|
|
if ( pLobbyEnter->m_EChatRoomEnterResponse != k_EChatRoomEnterResponseSuccess )
|
|
{
|
|
Warning( "CSysSessionConTeamHost: Could not join lobby\n" );
|
|
Failed();
|
|
}
|
|
else
|
|
{
|
|
m_eState = STATE_SEND_RESERVATION_REQUEST;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
XUID CSysSessionConTeamHost::GetHostXuid( XUID xuidValidResult )
|
|
{
|
|
return 0ull;
|
|
}
|
|
|
|
#ifdef _X360
|
|
|
|
bool CSysSessionConTeamHost::OnX360NetConnectionlessPacket( netpacket_t *pkt, KeyValues *msg )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#endif
|