|
|
//===== Copyright � 1996-2009, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "mm_framework.h"
#include "vstdlib/random.h"
#include "fmtstr.h"
#include "netmessages_signon.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// CMatchSessionOnlineClient
//
// Implementation of an online session of a host machine
//
void CMatchSessionOnlineClient::Init() { KeyValues *psessionHostDataUnpacked = m_pSettings->FindKey( "sessionHostDataUnpacked" ); if ( psessionHostDataUnpacked ) { m_pSettings->SetPtr( "options/sessionHostData", psessionHostDataUnpacked ); } }
CMatchSessionOnlineClient::CMatchSessionOnlineClient( KeyValues *pSettings ) : m_pSettings( pSettings->MakeCopy() ), m_autodelete_pSettings( m_pSettings ), m_pSysData( new KeyValues( "SysSessionData", "type", "client" ) ), m_autodelete_pSysData( m_pSysData ), m_eState( STATE_INIT ), m_pSysSession( NULL ) { DevMsg( "Created CMatchSessionOnlineClient:\n" ); Init();
KeyValuesDumpAsDevMsg( m_pSettings, 1 );
InitializeGameSettings(); }
CMatchSessionOnlineClient::CMatchSessionOnlineClient( CSysSessionClient *pSysSession, KeyValues *pSettings ) : m_pSettings( pSettings ), m_autodelete_pSettings( m_pSettings ), m_pSysData( new KeyValues( "SysSessionData", "type", "client" ) ), m_autodelete_pSysData( m_pSysData ), m_eState( STATE_LOBBY ), m_pSysSession( pSysSession ) { DevMsg( "Converted sys session into CMatchSessionOnlineClient:\n" ); Init(); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); }
CMatchSessionOnlineClient::CMatchSessionOnlineClient( CSysSessionHost *pSysSession, KeyValues *pExtendedSettings ) : m_pSettings( pExtendedSettings->FindKey( "settings" ) ), m_autodelete_pSettings( m_pSettings ), m_pSysData( new KeyValues( "SysSessionData", "type", "client" ) ), m_autodelete_pSysData( m_pSysData ), m_eState( STATE_LOBBY ), // it's at least lobby, we'll figure out later
m_pSysSession( NULL ) { DevMsg( "Migrating into CMatchSessionOnlineClient...\n" ); Init(); Assert( m_pSettings );
KeyValues::AutoDelete autodelete( pExtendedSettings ); pExtendedSettings->RemoveSubKey( m_pSettings ); // it's now our settings
// Install our session
g_pMMF->SetCurrentMatchSession( this );
// Check if the game is in a non-lobby state
char const *szState = pExtendedSettings->GetString( "state", "" ); if ( !Q_stricmp( szState, "game" ) ) m_eState = STATE_GAME; else if ( !Q_stricmp( szState, "ending" ) ) m_eState = STATE_ENDING;
// Now we need to create the system session to reflect the client session passed
m_pSysSession = new CSysSessionClient( pSysSession, m_pSettings ); pSysSession->Destroy();
// Show the state
DevMsg( "Migrated into CMatchSessionOnlineClient:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); }
CMatchSessionOnlineClient::~CMatchSessionOnlineClient() { DevMsg( "Destroying CMatchSessionOnlineClient:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); }
KeyValues * CMatchSessionOnlineClient::GetSessionSystemData() { // Setup our sys data
m_pSysData->SetUint64( "xuidReserve", m_pSysSession ? m_pSysSession->GetReservationCookie() : 0ull ); m_pSysData->SetUint64( "xuidHost", m_pSysSession ? m_pSysSession->GetHostXuid() : 0ull );
switch ( m_eState ) { case STATE_LOBBY: m_pSysData->SetString( "state", "lobby" ); break; case STATE_GAME: m_pSysData->SetString( "state", "game" ); break; default: m_pSysData->SetString( "state", "" ); break; }
return m_pSysData; }
KeyValues * CMatchSessionOnlineClient::GetSessionSettings() { return m_pSettings; }
void CMatchSessionOnlineClient::UpdateSessionSettings( KeyValues *pSettings ) { // Avoid a warning and assert for queue state manipulation
if ( pSettings->GetFirstSubKey()->GetString( "game/mmqueue", NULL ) ) return;
// Otherwise warn
Warning( "CMatchSessionOnlineClient::UpdateSessionSettings is unavailable in state %d!\n", m_eState ); Assert( !"CMatchSessionOnlineClient::UpdateSessionSettings is unavailable!\n" ); }
void CMatchSessionOnlineClient::UpdateTeamProperties( KeyValues *pSettings ) { m_pSysSession->UpdateTeamProperties( pSettings ); }
void CMatchSessionOnlineClient::Command( KeyValues *pCommand ) { char const *szCommand, *szRun; szCommand = pCommand->GetName(); szRun = pCommand->GetString( "run", "" );
if ( !Q_stricmp( szRun, "host" ) || !Q_stricmp( szRun, "all" ) || !Q_stricmp( szRun, "xuid" ) ) { if ( m_pSysSession ) { m_pSysSession->Command( pCommand ); return; } } else if ( !*szRun || !Q_stricmp( szRun, "local" ) ) { OnRunCommand( pCommand ); return; }
Warning( "CMatchSessionOnlineClient::Command( %s ) unhandled!\n", szCommand ); Assert( !"CMatchSessionOnlineClient::Command" ); }
void CMatchSessionOnlineClient::OnRunCommand( KeyValues *pCommand ) { char const *szCommand = pCommand->GetName(); if ( !Q_stricmp( "Migrate", szCommand ) ) { if ( m_pSysSession ) // TODO: research who sends the "Migrate" command and how secure it is?
{ m_pSysSession->Migrate( pCommand ); return; } } if ( !Q_stricmp( "QueueConnect", szCommand ) ) { if ( ( m_eState == STATE_LOBBY ) && !pCommand->GetUint64( "_remote_xuidsrc" ) ) { OnRunCommand_QueueConnect( pCommand ); return; } }
// Client-side command cannot update the players
g_pMMF->GetMatchTitleGameSettingsMgr()->ExecuteCommand( pCommand, GetSessionSystemData(), m_pSettings, NULL );
// Send the command as event for handling
KeyValues *pEvent = pCommand->MakeCopy(); pEvent->SetName( CFmtStr( "Command::%s", pCommand->GetName() ) ); g_pMatchEventsSubscription->BroadcastEvent( pEvent ); }
uint64 CMatchSessionOnlineClient::GetSessionID() { if( m_pSysSession ) { return m_pSysSession->GetSessionID(); }
return 0; }
void CMatchSessionOnlineClient::Update() { switch ( m_eState ) { case STATE_INIT: m_eState = STATE_CREATING;
// Adjust invite-based settings
uint64 uiInviteFlags = g_pMMF->GetLastInviteFlags(); m_pSettings->SetUint64( "members/joinflags", uiInviteFlags );
// Session is creating
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "progress", "progress", "joining" ) );
// Trigger session creation
m_pSysSession = new CSysSessionClient( m_pSettings ); break; }
if ( m_pSysSession ) { m_pSysSession->Update(); } }
void CMatchSessionOnlineClient::Destroy() { if ( m_eState != STATE_MIGRATE ) { g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( 0ull, 0ull ); }
if ( m_eState == STATE_GAME || m_eState == STATE_ENDING ) { m_eState = STATE_INIT; // the object is being deleted, but prevent signon state change handlers
g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "disconnect" ); }
if ( m_pSysSession ) { m_pSysSession->Destroy(); m_pSysSession = NULL; }
delete this; }
void CMatchSessionOnlineClient::DebugPrint() { DevMsg( "CMatchSessionOnlineClient [ state=%d ]\n", m_eState ); DevMsg( "System data:\n" ); KeyValuesDumpAsDevMsg( GetSessionSystemData(), 1 ); DevMsg( "Settings data:\n" ); KeyValuesDumpAsDevMsg( GetSessionSettings(), 1 ); if ( m_pSysSession ) m_pSysSession->DebugPrint(); else DevMsg( "SysSession is NULL\n" ); }
bool CMatchSessionOnlineClient::IsAnotherSessionJoinable( const char *pszAnotherSessionInfo ) { #ifdef _X360
if ( m_pSysSession ) { XSESSION_INFO xsi; if ( m_pSysSession->GetHostNetworkAddress( xsi ) ) { XSESSION_INFO xsiAnother; MMX360_SessionInfoFromString( xsiAnother, pszAnotherSessionInfo ); if ( !memcmp( &xsiAnother.sessionID, &xsi.sessionID, sizeof( xsi.sessionID ) ) ) return false; } } #endif
return true; }
void CMatchSessionOnlineClient::OnEvent( KeyValues *pEvent ) { char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnEngineClientSignonStateChange", szEvent ) ) { int iOldState = pEvent->GetInt( "old", 0 ); int iNewState = pEvent->GetInt( "new", 0 );
if ( iOldState >= SIGNONSTATE_CONNECTED && iNewState < SIGNONSTATE_CONNECTED ) { if ( m_eState == STATE_GAME ) { // Lost connection from server or explicit disconnect
DevMsg( "OnEngineClientSignonStateChange\n" ); g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mmF->CloseSession" ) ); } else if ( m_eState == STATE_ENDING ) { m_eState = STATE_LOBBY; g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "ready", "transition", "clientendgame" ) ); } } } else if ( !Q_stricmp( "OnEngineDisconnectReason", szEvent ) ) { if ( m_eState == STATE_GAME ) { // Lost connection from server or explicit disconnect
char const *szReason = pEvent->GetString( "reason", "" ); DevMsg( "OnEngineDisconnectReason %s\n", szReason );
bool bLobbySalvagable = StringHasPrefix( szReason, "Connection to server timed out" ) || StringHasPrefix( szReason, "Server shutting down" );
if ( KeyValues *pDisconnectHdlr = g_pMMF->GetMatchTitleGameSettingsMgr()->PrepareClientLobbyForGameDisconnect( m_pSettings, pEvent ) ) { KeyValues::AutoDelete autodelete( pDisconnectHdlr ); char const *szDisconnectHdlr = pDisconnectHdlr->GetString( "disconnecthdlr", "" ); if ( !Q_stricmp( szDisconnectHdlr, "destroy" ) ) bLobbySalvagable = false; else if ( !Q_stricmp( szDisconnectHdlr, "lobby" ) ) bLobbySalvagable = true; }
if ( !bLobbySalvagable ) { g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "mmF->CloseSession" ) ); } else { // Server shutting down, try to retain the lobby
pEvent->SetString( "disconnecthdlr", "lobby" ); g_pMatchEventsSubscription->RegisterEventData( pEvent->MakeCopy() ); OnEndGameToLobby(); } } } else if ( !Q_stricmp( "OnEngineEndGame", szEvent ) ) { if ( m_eState == STATE_GAME ) { OnEndGameToLobby(); } } else if ( !Q_stricmp( "OnMatchSessionUpdate", szEvent ) ) { char const *szState = pEvent->GetString( "state", "" ); if ( !Q_stricmp( "updated", szState ) ) { switch ( m_eState ) { case STATE_LOBBY: if ( KeyValues *pServer = pEvent->FindKey( "update/server" ) ) { // If we are in a con team match we only connect to game servers
// in response to an invite from the host so don't do anything
KeyValues *pConTeamMatch = m_pSettings->FindKey("options/conteammatch"); if ( !pConTeamMatch || ( pEvent->GetUint64( "update/server/xuid" ) && pEvent->GetUint64( "update/server/reservationid" ) ) ) { // Game server has become available - connect to it!
ConnectGameServer(); } } break; case STATE_GAME: if ( KeyValues *pServer = pEvent->FindKey( "delete/server" ) ) { // Lobby leader has disconnected from game server, disconnect too
OnEndGameToLobby(); } break; } } } else if ( !Q_stricmp( "OnNetLanConnectionlessPacket", szEvent ) ) { char const *szPacketType = pEvent->GetFirstTrueSubKey()->GetName(); if ( m_pSysSession && m_eState > STATE_CREATING && !Q_stricmp( szPacketType, "LanSearch" ) ) { m_pSysSession->ReplyLanSearch( pEvent ); } } else if ( !Q_stricmp( "OnSysMuteListChanged", szEvent ) ) { if ( m_pSysSession ) m_pSysSession->Voice_UpdateMutelist(); } else if ( !Q_stricmp( "mmF->SysSessionUpdate", szEvent ) ) { if ( m_pSysSession && pEvent->GetPtr( "syssession", NULL ) == m_pSysSession ) { // This is our session
if ( char const *szError = pEvent->GetString( "error", NULL ) ) { // Destroy the session
m_pSysSession->Destroy(); m_pSysSession = NULL; m_eState = STATE_CREATING;
// Handle error
KeyValues *kvUpdateError = pEvent->MakeCopy(); kvUpdateError->SetName( "OnMatchSessionUpdate" ); kvUpdateError->SetString( "state", "error" ); g_pMatchEventsSubscription->BroadcastEvent( kvUpdateError ); return; }
if ( uint64 ullCrypt = pEvent->GetUint64( "crypt" ) ) m_pSysData->SetUint64( "crypt", ullCrypt );
switch ( m_eState ) { case STATE_CREATING: // Session was creating
// Now we are at least in the lobby
m_eState = STATE_LOBBY; OnClientFullyConnectedToSession(); return; default: if ( char const *szAction = pEvent->GetString( "action", NULL ) ) { if ( !Q_stricmp( "host", szAction ) ) { KeyValues *pExtendedSettings = new KeyValues( "ExtendedSettings" ); char const *szMigrateState = "lobby"; switch ( m_eState ) { case STATE_GAME: szMigrateState = "game"; break; case STATE_ENDING: szMigrateState = "ending"; break; } pExtendedSettings->SetString( "state", szMigrateState ); if ( uint64 ullCrypt = m_pSysData->GetUint64( "crypt" ) ) pExtendedSettings->SetUint64( "crypt", ullCrypt ); pExtendedSettings->AddSubKey( m_pSettings ); // Release ownership of the resources since new match session now owns them
m_pSettings = NULL; m_autodelete_pSettings.Assign( NULL );
CSysSessionClient *pSysSession = m_pSysSession; m_pSysSession = NULL;
// Destroy our instance and create the new match interface
m_eState = STATE_MIGRATE; g_pMMF->SetCurrentMatchSession( NULL ); this->Destroy();
// Now we need to create the new host session that will install itself
IMatchSession *pNewHost = new CMatchSessionOnlineHost( pSysSession, pExtendedSettings ); Assert( g_pMMF->GetMatchSession() == pNewHost ); pNewHost; return; } } break; }
DevWarning( "Unhandled mmF->SysSessionUpdate!\n" ); Assert( !"Unhandled mmF->SysSessionUpdate!\n" ); } } else if ( !Q_stricmp( "mmF->SysSessionCommand", szEvent ) ) { if ( m_pSysSession && pEvent->GetPtr( "syssession", NULL ) == m_pSysSession ) { KeyValues *pCommand = pEvent->GetFirstTrueSubKey(); if ( pCommand ) { OnRunCommand( pCommand ); } } } }
void CMatchSessionOnlineClient::OnClientFullyConnectedToSession() { g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "created" ) );
// Check whether the game server is available (i.e. game in progress)
if ( KeyValues *pServer = m_pSettings->FindKey( "server" ) ) { ConnectGameServer(); } else { g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "ready", "transition", "clientconnect" ) ); } }
void CMatchSessionOnlineClient::OnEndGameToLobby() { m_eState = STATE_ENDING;
// Issue the disconnect command
g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "disconnect" ); // Mark gameplay state as inactive
m_pSysSession->SetSessionActiveGameplayState( false, NULL ); }
void CMatchSessionOnlineClient::InitializeGameSettings() { // Initialize only the settings required to connect...
if ( KeyValues *kv = m_pSettings->FindKey( "system", true ) ) { KeyValuesAddDefaultString( kv, "network", "LIVE" ); KeyValuesAddDefaultString( kv, "access", "public" ); }
if ( KeyValues *pMembers = m_pSettings->FindKey( "members", true ) ) { pMembers->SetInt( "numMachines", 1 );
int numPlayers = 1; #ifdef _GAMECONSOLE
numPlayers = XBX_GetNumGameUsers(); #endif
pMembers->SetInt( "numPlayers", numPlayers ); pMembers->SetInt( "numSlots", numPlayers );
if ( KeyValues *pMachine = pMembers->FindKey( "machine0", true ) ) { XUID machineid = g_pPlayerManager->GetLocalPlayer( XBX_GetPrimaryUserId() )->GetXUID();
pMachine->SetUint64( "id", machineid ); #if defined( _PS3 ) && !defined( NO_STEAM )
pMachine->SetUint64( "psnid", steamapicontext->SteamUser()->GetConsoleSteamID().ConvertToUint64() ); #endif
pMachine->SetUint64( "flags", MatchSession_GetMachineFlags() ); pMachine->SetInt( "numPlayers", numPlayers ); pMachine->SetUint64( "dlcmask", g_pMatchFramework->GetMatchSystem()->GetDlcManager()->GetDataInfo()->GetUint64( "@info/installed" ) ); pMachine->SetString( "tuver", MatchSession_GetTuInstalledString() ); pMachine->SetInt( "ping", 0 );
for ( int k = 0; k < numPlayers; ++ k ) { if ( KeyValues *pPlayer = pMachine->FindKey( CFmtStr( "player%d", k ), true ) ) { int iController = 0; #ifdef _GAMECONSOLE
iController = XBX_GetUserId( k ); #endif
IPlayerLocal *player = g_pPlayerManager->GetLocalPlayer( iController );
pPlayer->SetUint64( "xuid", player->GetXUID() ); pPlayer->SetString( "name", player->GetName() ); } } } }
// Let the title extend the game settings
g_pMMF->GetMatchTitleGameSettingsMgr()->InitializeGameSettings( m_pSettings, "client" );
DevMsg( "CMatchSessionOnlineClient::InitializeGameSettings adjusted settings:\n" ); KeyValuesDumpAsDevMsg( m_pSettings, 1 ); }
void CMatchSessionOnlineClient::OnRunCommand_QueueConnect( KeyValues *pCommand ) { char const *szConnectAddress = pCommand->GetString( "adronline", "0.0.0.0" ); uint64 uiReservationId = pCommand->GetUint64( "reservationid" ); bool bAutoCloseSession = pCommand->GetBool( "auto_close_session" );
// Switch the state
m_eState = STATE_GAME;
MatchSession_PrepareClientForConnect( m_pSettings, uiReservationId );
// Mark gameplay state as active
m_pSysSession->SetSessionActiveGameplayState( true, szConnectAddress );
// Close the session, potentially resetting a bunch of state
if ( bAutoCloseSession ) g_pMatchFramework->CloseSession();
// Determine reservation settings required
g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( uiReservationId, 0ull );
// Issue the connect command
g_pMatchExtensions->GetIVEngineClient()->StartLoadingScreenForCommand( CFmtStr( "connect %s", szConnectAddress ) ); }
void CMatchSessionOnlineClient::ConnectGameServer() { // Switch the state
m_eState = STATE_GAME;
MatchSession_PrepareClientForConnect( m_pSettings );
//
// Resolve server information
//
MatchSessionServerInfo_t msInfo = {0}; if ( !MatchSession_ResolveServerInfo( m_pSettings, m_pSysSession, msInfo, msInfo.RESOLVE_DEFAULT, m_pSysData->GetUint64( "crypt" ) ) ) { // Destroy the session
m_pSysSession->Destroy(); m_pSysSession = NULL;
// Handle error
g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnMatchSessionUpdate", "state", "error", "error", "connect" ) ); return; }
// Determine reservation settings required
g_pMatchExtensions->GetINetSupport()->UpdateClientReservation( msInfo.m_uiReservationCookie, msInfo.m_xuidJingle );
// Mark gameplay state as active
m_pSysSession->SetSessionActiveGameplayState( true, msInfo.m_szSecureServerAddress );
// Issue the connect command
g_pMatchExtensions->GetIVEngineClient()->ClientCmd_Unrestricted( msInfo.m_szConnectCmd ); }
|