//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// // // Purpose: // //=====================================================================================// #include "mm_framework.h" #ifndef _X360 #include "xbox/xboxstubs.h" #endif #include "smartptr.h" #include "utlvector.h" #include "x360_lobbyapi.h" #include "leaderboards.h" #include "igameevents.h" #include "GameUI/IGameUI.h" #include "filesystem.h" // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" #define STEAM_PACK_BITFIELDS ConVar cl_names_debug( "cl_names_debug", "0", FCVAR_DEVELOPMENTONLY ); #define PLAYER_DEBUG_NAME "WWWWWWWWWWWWWWW" #ifndef NO_STEAM static float s_flSteamStatsRequestTime = 0; static bool s_bSteamStatsRequestFailed = false; bool g_bSteamStatsReceived = false; #endif static DWORD GetTitleSpecificDataId( int idx ) { static DWORD arrTSDI[3] = { XPROFILE_TITLE_SPECIFIC1, XPROFILE_TITLE_SPECIFIC2, XPROFILE_TITLE_SPECIFIC3 }; if ( idx >= 0 && idx < ARRAYSIZE( arrTSDI ) ) return arrTSDI[idx]; return DWORD(-1); } static int GetTitleSpecificDataIndex( DWORD TSDataId ) { switch( TSDataId ) { case XPROFILE_TITLE_SPECIFIC1: return 0; case XPROFILE_TITLE_SPECIFIC2: return 1; case XPROFILE_TITLE_SPECIFIC3: return 2; default: return -1; } } static MM_XWriteOpportunity s_arrXWO[ XUSER_MAX_COUNT ]; // rely on static memory being zero'd #ifdef _X360 CUtlVector< PlayerLocal::XPendingAsyncAward_t * > PlayerLocal::s_arrPendingAsyncAwards; #endif void SignalXWriteOpportunity( MM_XWriteOpportunity eXWO ) { if ( !eXWO ) { Warning( "SignalXWriteOpportunity called with MMXWO_NONE!\n" ); return; } else { Msg( "SignalXWriteOpportunity(%d)\n", eXWO ); } // In case session has just started we bump every player's last write time // to the current time since player shouldn't write within the first 5 mins // from when the session started (TCR) if ( eXWO == MMXWO_SESSION_STARTED ) { for ( int k = 0; k < XUSER_MAX_COUNT; ++ k ) { IPlayerLocal *pLocalPlayer = g_pPlayerManager->GetLocalPlayer( k ); if ( PlayerLocal *pPlayer = dynamic_cast< PlayerLocal * >( pLocalPlayer ) ) { pPlayer->SetTitleDataWriteTime( Plat_FloatTime() ); } } return; } for ( int k = 0; k < ARRAYSIZE( s_arrXWO ); ++ k ) { // Only elevate write opportunity: // this way any other code can signal CHECKPOINT after SESSION_FINISHED // and the write will happen as SESSION_FINISHED if ( s_arrXWO[k] < eXWO ) s_arrXWO[k] = eXWO; } } MM_XWriteOpportunity GetXWriteOpportunity( int iCtrlr ) { if ( iCtrlr >= 0 && iCtrlr < ARRAYSIZE( s_arrXWO ) ) { MM_XWriteOpportunity result = s_arrXWO[ iCtrlr ]; s_arrXWO[ iCtrlr ] = MMXWO_NONE; // reset return result; } else return MMXWO_NONE; } static CUtlVector< XUID > s_arrSessionSearchesQueue; static int s_numSearchesOutstanding = 0; ConVar mm_player_search_count( "mm_player_search_count", "5", FCVAR_DEVELOPMENTONLY ); void PumpSessionSearchQueue() { while ( s_arrSessionSearchesQueue.Count() > 0 && s_numSearchesOutstanding < mm_player_search_count.GetInt() ) { XUID xid = s_arrSessionSearchesQueue[0]; s_arrSessionSearchesQueue.Remove( 0 ); if ( PlayerFriend *player = g_pPlayerManager->FindPlayerFriend( xid ) ) { player->StartSearchForSessionInfoImpl(); } } } // // PlayerFriend implementation // PlayerFriend::PlayerFriend( XUID xuid, FriendInfo_t const *pFriendInfo /* = NULL */ ) : m_uFriendMark( 0 ), m_bIsStale( false ), m_eSearchState( SEARCH_NONE ), m_pDetails( NULL ), m_pPublishedPresence( NULL ) { memset( m_wszRichPresence, 0, sizeof( m_wszRichPresence ) ); memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); m_uiTitleID = 0; m_uiGameServerIP = 0; #ifdef _X360 memset( &m_xsiSearchState, 0, sizeof( m_xsiSearchState ) ); m_pQOS_xnaddr = NULL; m_pQOS_xnkid = NULL; m_pQOS_xnkey = NULL; m_XNQOS = NULL; memset( &m_SessionSearchOverlapped, 0, sizeof( m_SessionSearchOverlapped ) ); #endif m_xuid = xuid; m_eOnlineState = STATE_ONLINE; UpdateFriendInfo( pFriendInfo ); } wchar_t const * PlayerFriend::GetRichPresence() { return m_wszRichPresence; } KeyValues * PlayerFriend::GetGameDetails() { return m_pDetails; } KeyValues * PlayerFriend::GetPublishedPresence() { return m_pPublishedPresence; } bool PlayerFriend::IsJoinable() { if ( m_pPublishedPresence && m_pPublishedPresence->GetString( "connect" )[0] ) return true; // joining via connect string if ( !( const uint64 & ) m_xSessionID ) return false; if ( m_pDetails->GetInt( "members/numSlots" ) <= m_pDetails->GetInt( "members/numPlayers" ) ) return false; if ( *m_pDetails->GetString( "system/lock" ) ) return false; if ( !Q_stricmp( "private", m_pDetails->GetString( "system/access" ) ) ) return false; return true; // joining via lobby } uint64 PlayerFriend::GetTitleID() { return m_uiTitleID; } uint32 PlayerFriend::GetGameServerIP() { return m_uiGameServerIP; } void PlayerFriend::Join() { // Requesting to join this player KeyValues *pSettings = KeyValues::FromString( "settings", " system { " " network LIVE " " } " " options { " " action joinsession " " } " ); if ( m_eSearchState == SEARCH_NONE ) { pSettings->SetString( "system/network", m_pDetails->GetString( "system/network", "LIVE" ) ); } pSettings->SetUint64( "options/sessionid", ( const uint64 & ) m_xSessionID ); pSettings->SetUint64( "options/friendxuid", m_xuid ); #ifdef _X360 char chSessionInfoBuffer[ XSESSION_INFO_STRING_LENGTH ] = {0}; MMX360_SessionInfoToString( m_GameSessionInfo, chSessionInfoBuffer ); pSettings->SetString( "options/sessioninfo", chSessionInfoBuffer ); #endif KeyValues::AutoDelete autodelete( pSettings ); g_pMatchFramework->MatchSession( pSettings ); } void PlayerFriend::Update() { if ( !m_xuid ) return; #ifdef _X360 if( m_eSearchState == SEARCH_XNKID ) { Live_Update_SearchXNKID(); } if ( m_eSearchState == SEARCH_QOS ) { Live_Update_Search_QOS(); } #endif if ( m_eSearchState == SEARCH_COMPLETED ) { m_eSearchState = SEARCH_NONE; -- s_numSearchesOutstanding; PumpSessionSearchQueue(); if( V_memcmp( &( m_GameSessionInfo.sessionID ), &m_xSessionID, sizeof( m_xSessionID ) ) != 0) { // Re-discover everything again since session ID changed StartSearchForSessionInfo(); } // Signal that we are finished with a search KeyValues *kvEvent = new KeyValues( "OnMatchPlayerMgrUpdate", "update", "friend" ); kvEvent->SetUint64( "xuid", GetXUID() ); g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent ); } } #ifdef _X360 #ifdef _DEBUG static ConVar mm_player_delay_xnkid( "mm_player_delay_xnkid", "0", FCVAR_DEVELOPMENTONLY ); static ConVar mm_player_delay_qos( "mm_player_delay_qos", "0", FCVAR_DEVELOPMENTONLY ); static bool ShouldDelayBasedOnTimeThrottling( float &flStaticTimekeeper, float flDelay ) { if ( flDelay <= 0.0f ) { flStaticTimekeeper = 0.0f; return false; } else if ( flStaticTimekeeper <= 0.0f ) { flStaticTimekeeper = Plat_FloatTime(); return true; } else if ( flStaticTimekeeper + flDelay < Plat_FloatTime() ) { flStaticTimekeeper = 0.0f; return false; } else { return true; } } static bool ShouldDelayPlayerXnkid() { static float s_flTime = 0.0f; return ShouldDelayBasedOnTimeThrottling( s_flTime, mm_player_delay_xnkid.GetFloat() ); } static bool ShouldDelayPlayerQos() { static float s_flTime = 0.0f; return ShouldDelayBasedOnTimeThrottling( s_flTime, mm_player_delay_qos.GetFloat() ); } #else inline static bool ShouldDelayPlayerXnkid() { return false; } inline static bool ShouldDelayPlayerQos() { return false; } #endif void PlayerFriend::Live_Update_SearchXNKID() { if( !XHasOverlappedIoCompleted( & m_SessionSearchOverlapped ) ) return; if ( ShouldDelayPlayerXnkid() ) return; DWORD result = 0; if( XGetOverlappedResult( &m_SessionSearchOverlapped, &result, false ) == ERROR_SUCCESS ) { //result should be 1 if( GetXSearchResults()->dwSearchResults >= 1) { V_memcpy( &m_GameSessionInfo, &( GetXSearchResults()->pResults[0].info ), sizeof( m_GameSessionInfo ) ); } else { memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; } } else { memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; } m_eSearchState = SEARCH_COMPLETED; if ( ( const uint64 & ) m_GameSessionInfo.sessionID ) { // Issue the QOS query m_xsiSearchState = m_GameSessionInfo; m_pQOS_xnaddr = &m_xsiSearchState.hostAddress; m_pQOS_xnkid = &m_xsiSearchState.sessionID; m_pQOS_xnkey = &m_xsiSearchState.keyExchangeKey; int err = g_pMatchExtensions->GetIXOnline()->XNetQosLookup( 1, &m_pQOS_xnaddr, &m_pQOS_xnkid, &m_pQOS_xnkey, 0, NULL, NULL, 2, 0, 0, NULL, &m_XNQOS ); if ( err == ERROR_SUCCESS ) m_eSearchState = SEARCH_QOS; } } void PlayerFriend::Live_Update_Search_QOS() { if( m_XNQOS->cxnqosPending != 0 ) return; if ( ShouldDelayPlayerQos() ) return; if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; XNQOSINFO *pQOS = &m_XNQOS->axnqosinfo[0]; if( pQOS->bFlags & XNET_XNQOSINFO_COMPLETE && pQOS->bFlags & XNET_XNQOSINFO_DATA_RECEIVED && pQOS->cbData && pQOS->pbData ) { MM_GameDetails_QOS_t gd = { pQOS->pbData, pQOS->cbData, pQOS->wRttMedInMsecs }; m_pDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromQOS( &gd ); } g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_XNQOS ); m_XNQOS = NULL; if ( m_pDetails ) { // Set AUX fields like sessioninfo if ( KeyValues *kvOptions = m_pDetails->FindKey( "options", true ) ) { kvOptions->SetUint64( "sessionid", ( const uint64 & ) m_xSessionID ); char chSessionInfoBuffer[ XSESSION_INFO_STRING_LENGTH ] = {0}; MMX360_SessionInfoToString( m_GameSessionInfo, chSessionInfoBuffer ); kvOptions->SetString( "sessioninfo", chSessionInfoBuffer ); } // Set the "player" key if ( KeyValues *kvPlayer = m_pDetails->FindKey( "player", true ) ) { kvPlayer->SetUint64( "xuid", GetXUID() ); kvPlayer->SetUint64( "xuidOnline", GetXUID() ); kvPlayer->SetString( "name", GetName() ); kvPlayer->SetWString( "richpresence", GetRichPresence() ); } } m_eSearchState = SEARCH_COMPLETED; } #elif !defined( NO_STEAM ) void PlayerFriend::Steam_OnLobbyDataUpdate( LobbyDataUpdate_t *pParam ) { // Only callbacks about lobby itself if ( pParam->m_ulSteamIDLobby != pParam->m_ulSteamIDMember ) return; // Listening for only callbacks related to current player if ( pParam->m_ulSteamIDLobby != ( const uint64 & ) m_xSessionID ) return; // Unregister the callback m_CallbackOnLobbyDataUpdate.Unregister(); // Set session info memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); m_GameSessionInfo.sessionID = m_xSessionID; // Describe the lobby if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; m_pDetails = g_pMatchFramework->GetMatchNetworkMsgController()->UnpackGameDetailsFromSteamLobby( pParam->m_ulSteamIDLobby ); if ( m_pDetails ) { // Set AUX fields like session id if ( KeyValues *kvOptions = m_pDetails->FindKey( "options", true ) ) { kvOptions->SetUint64( "sessionid", pParam->m_ulSteamIDLobby ); } // Set the "player" key if ( KeyValues *kvPlayer = m_pDetails->FindKey( "player", true ) ) { kvPlayer->SetUint64( "xuid", GetXUID() ); kvPlayer->SetUint64( "xuidOnline", GetXUID() ); kvPlayer->SetString( "name", GetName() ); kvPlayer->SetWString( "richpresence", GetRichPresence() ); } } m_eSearchState = SEARCH_COMPLETED; } #endif void PlayerFriend::Destroy() { AbortSearch(); if ( m_pPublishedPresence ) m_pPublishedPresence->deleteThis(); m_pPublishedPresence = NULL; delete this; } void PlayerFriend::AbortSearch() { #ifdef _X360 #elif !defined( NO_STEAM ) m_CallbackOnLobbyDataUpdate.Unregister(); #endif // Clean up the queue while ( s_arrSessionSearchesQueue.FindAndRemove( m_xuid ) ) continue; bool bAbortedSearch = false; switch ( m_eSearchState ) { #ifdef _X360 case SEARCH_XNKID: MMX360_CancelOverlapped( &m_SessionSearchOverlapped ); bAbortedSearch = true; break; case SEARCH_QOS: // We should gracefully abort the QOS operation outstanding g_pMatchExtensions->GetIXOnline()->XNetQosRelease( m_XNQOS ); m_XNQOS = NULL; bAbortedSearch = true; break; #elif !defined( NO_STEAM ) case SEARCH_WAIT_LOBBY_DATA: bAbortedSearch = true; break; #endif case SEARCH_COMPLETED: bAbortedSearch = true; break; } if ( bAbortedSearch ) { -- s_numSearchesOutstanding; PumpSessionSearchQueue(); } m_eSearchState = SEARCH_NONE; if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; } void PlayerFriend::SetFriendMark( unsigned maskSetting ) { m_uFriendMark = maskSetting; } unsigned PlayerFriend::GetFriendMark() { return m_uFriendMark; } void PlayerFriend::SetIsStale( bool bStale ) { m_bIsStale = bStale; } bool PlayerFriend::GetIsStale() { return m_bIsStale; } void PlayerFriend::UpdateFriendInfo( FriendInfo_t const *pFriendInfo ) { if ( !pFriendInfo ) return; if ( pFriendInfo->m_szName ) Q_strncpy( m_szName, pFriendInfo->m_szName, ARRAYSIZE( m_szName ) ); if ( pFriendInfo->m_wszRichPresence ) Q_wcsncpy( m_wszRichPresence, pFriendInfo->m_wszRichPresence, ARRAYSIZE( m_wszRichPresence ) ); m_uiTitleID = pFriendInfo->m_uiTitleID; if ( pFriendInfo->m_uiGameServerIP != ~0 ) m_uiGameServerIP = pFriendInfo->m_uiGameServerIP; if ( cl_names_debug.GetBool() ) { Q_strncpy( m_szName, PLAYER_DEBUG_NAME, ARRAYSIZE( m_szName ) ); } if ( pFriendInfo->m_pGameDetails ) { AbortSearch(); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = pFriendInfo->m_pGameDetails->MakeCopy(); #ifdef _X360 char const *szSessionInfo = m_pDetails->GetString( "options/sessioninfo" ); MMX360_SessionInfoFromString( m_GameSessionInfo, szSessionInfo ); m_xSessionID = m_GameSessionInfo.sessionID; #elif !defined( NO_STEAM ) uint64 uiSessionId = m_pDetails->GetUint64( "options/sessionid" ); m_xSessionID = ( XNKID & ) uiSessionId; #endif // Signal that we are finished with a search KeyValues *kvEvent = new KeyValues( "OnMatchPlayerMgrUpdate", "update", "friend" ); kvEvent->SetUint64( "xuid", GetXUID() ); g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( kvEvent ); } else if ( pFriendInfo->m_uiTitleID && pFriendInfo->m_uiTitleID != g_pMatchFramework->GetMatchTitle()->GetTitleID() ) { if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = new KeyValues( "TitleSettings" ); m_pDetails->SetUint64( "titleid", pFriendInfo->m_uiTitleID ); } else { m_xSessionID = pFriendInfo->m_xSessionID; if( m_eSearchState == SEARCH_NONE ) { StartSearchForSessionInfo(); } } #if !defined( NO_STEAM ) // Update published presence for the friend too if ( m_pPublishedPresence ) { m_pPublishedPresence->deleteThis(); m_pPublishedPresence = NULL; } ISteamFriends *pf = steamapicontext->SteamFriends(); if ( pf && ( g_pMatchFramework->GetMatchTitle()->GetTitleID() == m_uiTitleID ) ) { pf->RequestFriendRichPresence( GetXUID() ); // refresh friend's rich presence int numRichPresenceKeys = pf->GetFriendRichPresenceKeyCount( GetXUID() ); for ( int j = 0; j < numRichPresenceKeys; ++ j ) { const char *pszKey = pf->GetFriendRichPresenceKeyByIndex( GetXUID(), j ); if ( pszKey && *pszKey ) { char const *pszValue = pf->GetFriendRichPresence( GetXUID(), pszKey ); if ( pszValue && *pszValue ) { if ( !m_pPublishedPresence ) m_pPublishedPresence = new KeyValues( "RP" ); CFmtStr fmtNewKey( "%s", pszKey ); while ( char *szFixChar = strchr( fmtNewKey.Access(), ':' ) ) *szFixChar = '/'; m_pPublishedPresence->SetString( fmtNewKey, pszValue ); } } } } #endif } bool PlayerFriend::IsUpdatingInfo() { return m_eSearchState != SEARCH_NONE; } void PlayerFriend::StartSearchForSessionInfo() { if ( !m_xuid ) return; if ( m_eSearchState != SEARCH_NONE ) return; m_eSearchState = SEARCH_QUEUED; // Check if we are not already in the queue if ( s_arrSessionSearchesQueue.Find( m_xuid ) == s_arrSessionSearchesQueue.InvalidIndex() ) { s_arrSessionSearchesQueue.AddToTail( m_xuid ); } PumpSessionSearchQueue(); } void PlayerFriend::StartSearchForSessionInfoImpl() { #ifdef _X360 if ( !XBX_GetNumGameUsers() || XBX_GetPrimaryUserIsGuest() ) { memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; m_eSearchState = SEARCH_NONE; return; } #endif if( m_eSearchState == SEARCH_NONE || m_eSearchState == SEARCH_QUEUED ) { #ifdef _X360 if( ( const uint64 & ) m_xSessionID ) { int iCtrlr = XBX_GetPrimaryUserId(); DWORD numBytesResult = 0; DWORD dwError = g_pMatchExtensions->GetIXOnline()->XSessionSearchByID( m_xSessionID, iCtrlr, &numBytesResult, NULL, NULL ); if( dwError != ERROR_INSUFFICIENT_BUFFER ) { memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; m_eSearchState = SEARCH_NONE; return; } m_bufSessionSearchResults.EnsureCapacity( numBytesResult ); ZeroMemory( GetXSearchResults(), numBytesResult ); dwError = g_pMatchExtensions->GetIXOnline()->XSessionSearchByID( m_xSessionID, iCtrlr, &numBytesResult, GetXSearchResults(), &m_SessionSearchOverlapped ); if( dwError != ERROR_IO_PENDING ) { memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; m_eSearchState = SEARCH_NONE; return; } m_eSearchState = SEARCH_XNKID; #elif !defined( NO_STEAM ) if ( steamapicontext->SteamMatchmaking() && ( const uint64 & ) m_xSessionID && steamapicontext->SteamMatchmaking()->RequestLobbyData( ( const uint64 & ) m_xSessionID ) ) { // Enable the callback m_CallbackOnLobbyDataUpdate.Register( this, &PlayerFriend::Steam_OnLobbyDataUpdate ); m_eSearchState = SEARCH_WAIT_LOBBY_DATA; #else if ( 0 ) { #endif ++ s_numSearchesOutstanding; } else { memset( &m_xSessionID, 0, sizeof( m_xSessionID ) ); memset( &m_GameSessionInfo, 0, sizeof( m_GameSessionInfo ) ); if ( m_pDetails ) m_pDetails->deleteThis(); m_pDetails = NULL; m_eSearchState = SEARCH_NONE; } } } // // PlayerLocal implementation // PlayerLocal::PlayerLocal( int iController ) : m_eLoadedTitleData( eXUserSigninState_NotSignedIn ), m_flLastSave( 0.0f ), m_uiPlayerFlags( 0 ), m_pLeaderboardData( new KeyValues( "Leaderboard" ) ), m_autodelete_pLeaderboardData( m_pLeaderboardData ) { Assert( iController >= 0 && iController < XUSER_MAX_COUNT ); memset( &m_ProfileData, 0, sizeof( m_ProfileData ) ); memset( m_bufTitleData, 0, sizeof( m_bufTitleData ) ); memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData ) ); m_iController = iController; GetXWriteOpportunity( iController ); // reset #ifdef _X360 m_bIsTitleDataValid = false; for ( int i=0; iSteamUser() ) { m_eOnlineState = steamapicontext->SteamUser()->BLoggedOn() ? IPlayer::STATE_ONLINE : IPlayer::STATE_OFFLINE; steamIDPlayer = steamapicontext->SteamUser()->GetSteamID(); m_xuid = steamIDPlayer.IsValid() ? steamIDPlayer.ConvertToUint64() : 0; } else { m_xuid = 0; } #else m_xuid = 1ull; m_eOnlineState = IPlayer::STATE_OFFLINE; #endif #ifdef _X360 if( m_xuid ) { XUserGetName( m_iController, m_szName, ARRAYSIZE( m_szName ) ); LoadPlayerProfileData(); } else if ( char const *szGuestName = g_pMatchFramework->GetMatchTitle()->GetGuestPlayerName( m_iController ) ) { Q_strncpy( m_szName, szGuestName, ARRAYSIZE( m_szName ) ); } else { m_szName[0] = 0; } #elif defined ( _PS3 ) ConVarRef cl_name( "name" ); const char* pPlayerName = cl_name.GetString(); Q_strncpy( m_szName, pPlayerName, ARRAYSIZE( m_szName ) ); #elif !defined( NO_STEAM ) // Get user name from Steam if ( steamIDPlayer.IsValid() && steamapicontext->SteamUser() && steamapicontext->SteamFriends() ) { const char *pszName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamIDPlayer ); if ( pszName ) { Q_strncpy( m_szName, pszName, ARRAYSIZE( m_szName ) ); } } m_CallbackOnPersonaStateChange.Register( this, &PlayerLocal::Steam_OnPersonaStateChange ); m_CallbackOnServersConnected.Register( this, &PlayerLocal::Steam_OnServersConnected ); m_CallbackOnServersDisconnected.Register( this, &PlayerLocal::Steam_OnServersDisconnected ); LoadPlayerProfileData(); #else if ( char const *szGuestName = g_pMatchFramework->GetMatchTitle()->GetGuestPlayerName( m_iController ) ) { Q_strncpy( m_szName, szGuestName, ARRAYSIZE( m_szName ) ); } else { m_szName[0] = 0; } #endif if ( cl_names_debug.GetBool() ) { Q_strncpy( m_szName, PLAYER_DEBUG_NAME, ARRAYSIZE( m_szName ) ); } } PlayerLocal::~PlayerLocal() { #ifdef _X360 for ( int k = 0; k < s_arrPendingAsyncAwards.Count(); ++ k ) { // Detach pending achievement awards from currently destructed player if ( s_arrPendingAsyncAwards[k]->m_pLocalPlayer == this ) s_arrPendingAsyncAwards[k]->m_pLocalPlayer = NULL; } #endif } void PlayerLocal::LoadTitleData() { if ( m_eLoadedTitleData == eXUserSigninState_SignedInToLive ) // already processed return; if ( m_eLoadedTitleData >= GetAssumedSigninState() ) // already processed return; #ifdef _X360 m_bIsTitleDataValid = false; for ( int i=0; i vBuffer; vBuffer.SetCount( dwBytes ); ret = XEnumerate( hEnumerator, vBuffer.Base(), dwBytes, &numAchievements, NULL ); CloseHandle( hEnumerator ); hEnumerator = NULL; if ( ret == ERROR_SUCCESS ) { XACHIEVEMENT_DETAILS const *pXboxAchievements = ( XACHIEVEMENT_DETAILS const * ) vBuffer.Base(); for ( DWORD i = 0; i < numAchievements; ++i ) { if ( AchievementEarned( pXboxAchievements[i].dwFlags ) ) { m_arrAchievementsEarned.FindAndFastRemove( pXboxAchievements[i].dwId ); m_arrAchievementsEarned.AddToTail( pXboxAchievements[i].dwId ); } } } } } // // Load actual title data blocks // DWORD dwNumDataIds = TITLE_DATA_COUNT_X360; CArrayAutoPtr< DWORD > pdwTitleDataIds( new DWORD[ dwNumDataIds ] ); for ( DWORD k = 0; k < dwNumDataIds; ++ k ) pdwTitleDataIds[k] = GetTitleSpecificDataId( k ); m_eLoadedTitleData = GetAssumedSigninState(); DWORD resultsSize = 0; DWORD ret = ERROR_FILE_NOT_FOUND; if ( m_eLoadedTitleData == eXUserSigninState_SignedInLocally ) { ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings( g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController, dwNumDataIds, pdwTitleDataIds.Get(), &resultsSize, NULL, NULL ); } else if ( m_eLoadedTitleData == eXUserSigninState_SignedInToLive ) { ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettingsByXuid( g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController, 1, &m_xuid, dwNumDataIds, pdwTitleDataIds.Get(), &resultsSize, NULL, NULL ); } if ( ret != ERROR_INSUFFICIENT_BUFFER ) { Warning( "Player %d : LoadTitleData failed to get size (err=0x%08X)!\n", m_iController, ret ); // Failed OnProfileTitleDataLoaded( ret ); return; } CArrayAutoPtr< char > spResultBuffer( new char[ resultsSize ] ); XUSER_READ_PROFILE_SETTING_RESULT *pResult = (XUSER_READ_PROFILE_SETTING_RESULT *) spResultBuffer.Get(); if ( m_eLoadedTitleData == eXUserSigninState_SignedInLocally ) { ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings( g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController, dwNumDataIds, pdwTitleDataIds.Get(), &resultsSize, pResult, NULL ); } else if ( m_eLoadedTitleData == eXUserSigninState_SignedInToLive ) { ret = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettingsByXuid( g_pMatchFramework->GetMatchTitle()->GetTitleID(), m_iController, 1, &m_xuid, dwNumDataIds, pdwTitleDataIds.Get(), &resultsSize, pResult, NULL ); } if ( ret != ERROR_SUCCESS ) { Warning( "Player %d : LoadTitleData failed to read data (err=0x%08X)!\n", m_iController, ret ); // Failed OnProfileTitleDataLoaded( ret ); return; } m_bIsTitleDataValid = true; m_bIsFreshPlayerProfile = true; for ( DWORD iSetting = 0; iSetting < pResult->dwSettingsLen; ++ iSetting ) { XUSER_PROFILE_SETTING const &xps = pResult->pSettings[ iSetting ]; if ( xps.data.type != XUSER_DATA_TYPE_BINARY ) { m_bIsTitleDataValid = false; m_bIsFreshPlayerProfile = false; continue; } if ( xps.data.binary.cbData != XPROFILE_SETTING_MAX_SIZE ) { if ( xps.data.binary.cbData != 0 ) { m_bIsTitleDataValid = false; } continue; } m_bIsFreshPlayerProfile = false; m_bIsTitleDataBlockValid[ iSetting ] = true; int iDataIndex = GetTitleSpecificDataIndex( xps.dwSettingId ); if ( iDataIndex >= 0 ) { Msg( "Player %d : LoadTitleData succeeded with Data%d\n", m_iController, iDataIndex ); V_memcpy( m_bufTitleData[ iDataIndex ], xps.data.binary.pbData, XPROFILE_SETTING_MAX_SIZE ); } } // Clear the dirty flag after dirty V_memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData[0] ) * TITLE_DATA_COUNT_X360 ); // After we loaded some title data, see if we need to retrospectively award achievements EvaluateAwardsStateBasedOnStats(); Msg( "Player %d : LoadTitleData finished (%.3f sec).\n", m_iController, Plat_FloatTime() - flTimeStart ); if ( m_bIsTitleDataValid ) OnProfileTitleDataLoaded( 0 ); else OnProfileTitleDataLoaded( 1 ); #elif !defined ( NO_STEAM ) // Always request user stats from Steam if ( steamapicontext->SteamUserStats() ) { m_eLoadedTitleData = GetAssumedSigninState(); m_CallbackOnUserStatsReceived.Register( this, &PlayerLocal::Steam_OnUserStatsReceived ); steamapicontext->SteamUserStats()->RequestCurrentStats(); s_flSteamStatsRequestTime = Plat_FloatTime(); if ( !s_bSteamStatsRequestFailed ) { DevMsg( "Requesting Steam stats... (%2.2f)\n", Plat_FloatTime() ); } } #else m_eLoadedTitleData = GetAssumedSigninState(); // Since we don't have Steam, we reset all configuration values and stats. IGameEvent *event = g_pMatchExtensions->GetIGameEventManager2()->CreateEvent( "reset_game_titledata" ); if ( event ) { event->SetInt( "controllerId", m_iController ); g_pMatchExtensions->GetIGameEventManager2()->FireEventClientSide( event ); } g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "ResetConfiguration", "iController", m_iController ) ); #endif } #if !defined( _X360 ) && !defined ( NO_STEAM ) ConVar mm_cfgoverride_file( "mm_cfgoverride_file", "", FCVAR_DEVELOPMENTONLY ); ConVar mm_cfgoverride_commit( "mm_cfgoverride_commit", "", FCVAR_DEVELOPMENTONLY ); ConVar mm_cfgdebug_mode( "mm_cfgdebug_mode", "0", FCVAR_DEVELOPMENTONLY ); static bool SetSteamStatWithPotentialOverride( char const *szField, int32 iValue ) { char const *szStatFieldSteamDB = szField; return steamapicontext->SteamUserStats() ? steamapicontext->SteamUserStats()->SetStat( szStatFieldSteamDB, iValue ) : false; } static bool SetSteamStatWithPotentialOverride( char const *szField, float fValue ) { char const *szStatFieldSteamDB = szField; return steamapicontext->SteamUserStats() ? steamapicontext->SteamUserStats()->SetStat( szStatFieldSteamDB, fValue ) : false; } template < typename T > static inline bool ApplySteamStatPotentialOverride( char const *szField, T *pValue, bool bResult, T (KeyValues::*pfn)( char const *, T ) ) { #ifdef _CERT return bResult; #else if ( mm_cfgdebug_mode.GetInt() > 0 ) { DevMsg( "[PlayerStats] '%s' = %d (0x%08X)\n", szField, (int32)*pValue, *(int32*)pValue ); } char const *szFile = mm_cfgoverride_file.GetString(); if ( !szFile || !*szFile ) return bResult; KeyValues *kvOverride = new KeyValues( "cfgoverride.kv" ); KeyValues::AutoDelete autodelete( kvOverride ); if ( !kvOverride->LoadFromFile( g_pFullFileSystem, szFile ) ) return bResult; if ( KeyValues *kvItemOverride = kvOverride->FindKey( "items" )->FindKey( szField ) ) { *pValue = ( kvItemOverride->*pfn )( "", 0 ); DevMsg( "[PlayerStats] '%s' overrides '%s' = '%s'\n", szFile, szField, kvItemOverride->GetString( "", "" ) ); if ( mm_cfgoverride_commit.GetBool() ) SetSteamStatWithPotentialOverride( szField, *pValue ); return true; } // Match by wildcard for ( KeyValues *kvWildcard = kvOverride->FindKey( "wildcards" )->GetFirstValue(); kvWildcard; kvWildcard = kvWildcard->GetNextValue() ) { char const *szWildcard = kvWildcard->GetName(); int nLen = Q_strlen( szWildcard ); if ( !nLen || ( szWildcard[nLen-1] != '*' ) ) continue; if ( (nLen <= 1) || !Q_strnicmp( szWildcard, szField, nLen - 1 ) ) { *pValue = ( kvWildcard->*pfn )( "", 0 ); DevMsg( "[PlayerStats] '%s' overrides '%s' = '%s' [wildcard match '%s']\n", szFile, szField, kvWildcard->GetString( "", "" ), szWildcard ); if ( mm_cfgoverride_commit.GetBool() ) SetSteamStatWithPotentialOverride( szField, *pValue ); return true; } } return bResult; #endif } static bool GetSteamStatWithPotentialOverride( char const *szField, int32 *pValue ) { char const *szStatFieldSteamDB = szField; bool bResult = steamapicontext->SteamUserStats()->GetStat( szStatFieldSteamDB, pValue ); return ApplySteamStatPotentialOverride( szField, pValue, bResult, &KeyValues::GetInt ); } static bool GetSteamStatWithPotentialOverride( char const *szField, float *pValue ) { char const *szStatFieldSteamDB = szField; bool bResult = steamapicontext->SteamUserStats()->GetStat( szStatFieldSteamDB, pValue ); return ApplySteamStatPotentialOverride( szField, pValue, bResult, &KeyValues::GetFloat ); } void PlayerLocal::Steam_OnUserStatsReceived( UserStatsReceived_t *pParam ) { #ifndef NO_STEAM if ( !s_bSteamStatsRequestFailed || ( pParam->m_eResult == k_EResultOK ) ) { DevMsg( "PlayerLocal::Steam_OnUserStatsReceived... (%2.2f sec since request)\n", s_flSteamStatsRequestTime ? ( Plat_FloatTime() - s_flSteamStatsRequestTime ) : 0.0f ); } s_flSteamStatsRequestTime = 0; #endif // If failed, we'll request one more time if ( pParam->m_eResult != k_EResultOK ) { if ( !s_bSteamStatsRequestFailed ) { DevWarning( "PlayerLocal::Steam_OnUserStatsReceived (failed with error %d)\n", pParam->m_eResult ); s_bSteamStatsRequestFailed = true; } m_eLoadedTitleData = eXUserSigninState_NotSignedIn; return; } s_bSteamStatsRequestFailed = false; g_bSteamStatsReceived = true; // // Achievements state // for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements(); pAchievement && pAchievement->m_szAchievementName; ++ pAchievement ) { bool bAchieved; if ( steamapicontext->SteamUserStats()->GetAchievement( pAchievement->m_szAchievementName, &bAchieved ) && bAchieved ) { m_arrAchievementsEarned.FindAndFastRemove( pAchievement->m_idAchievement ); m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement ); } } // // Load all our stats data // TitleDataFieldsDescription_t const *pTitleDataTable = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(); for ( ; pTitleDataTable && pTitleDataTable->m_szFieldName; ++ pTitleDataTable ) { switch( pTitleDataTable->m_eDataType ) { case TitleDataFieldsDescription_t::DT_uint8: case TitleDataFieldsDescription_t::DT_uint16: case TitleDataFieldsDescription_t::DT_uint32: { uint32 i32field[3] = {0}; if ( GetSteamStatWithPotentialOverride( pTitleDataTable->m_szFieldName, ( int32 * ) &i32field[0] ) ) { *( uint16 * )( &i32field[1] ) = uint16( i32field[0] ); *( uint8 * )( &i32field[2] ) = uint8 ( i32field[0] ); memcpy( &m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset ], &i32field[ 2 - ( pTitleDataTable->m_eDataType / 16 ) ], pTitleDataTable->m_eDataType / 8 ); } } break; case TitleDataFieldsDescription_t::DT_float: { float flField = 0.0f; if ( GetSteamStatWithPotentialOverride( pTitleDataTable->m_szFieldName, &flField ) ) { memcpy( &m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset ], &flField, pTitleDataTable->m_eDataType / 8 ); } } break; case TitleDataFieldsDescription_t::DT_uint64: { uint32 i32field[2] = { 0 }; char chBuffer[ 256 ] = {0}; for ( int k = 0; k < ARRAYSIZE( i32field ); ++ k ) { Q_snprintf( chBuffer, ARRAYSIZE( chBuffer ), "%s.%d", pTitleDataTable->m_szFieldName, k ); if ( !GetSteamStatWithPotentialOverride( chBuffer, ( int32 * ) &i32field[k] ) ) i32field[k] = 0; } memcpy( &m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset ], &i32field[0], pTitleDataTable->m_eDataType / 8 ); } break; case TitleDataFieldsDescription_t::DT_BITFIELD: { #ifdef STEAM_PACK_BITFIELDS char chStatField[64] = {0}; uint32 uiOffsetInTermsOfUINT32 = pTitleDataTable->m_numBytesOffset/32; V_snprintf( chStatField, sizeof( chStatField ), "bitfield_%02u_%03X", pTitleDataTable->m_iTitleDataBlock + 1, uiOffsetInTermsOfUINT32*4 ); int32 iCombinedBitValue = 0; if ( GetSteamStatWithPotentialOverride( chStatField, &iCombinedBitValue ) ) { ( reinterpret_cast< uint32 * >( &m_bufTitleData[pTitleDataTable->m_iTitleDataBlock][0] ) )[ uiOffsetInTermsOfUINT32 ] = iCombinedBitValue; } #else int i32field = 0; if ( GetSteamStatWithPotentialOverride( pTitleDataTable->m_szFieldName, &i32field ) ) { char &rByte = m_bufTitleData[ pTitleDataTable->m_iTitleDataBlock ][ pTitleDataTable->m_numBytesOffset/8 ]; char iMask = ( 1 << ( pTitleDataTable->m_numBytesOffset % 8 ) ); if ( i32field ) rByte |= iMask; else rByte &=~iMask; } #endif } break; } } #if defined ( _PS3 ) // We just loaded all our stats and settings from Steam. TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(); Assert( fields ); TitleDataFieldsDescription_t const *versionField = TitleDataFieldsDescriptionFindByString( fields, TITLE_DATA_PREFIX "CFG.sys.version" ); Assert( versionField ); int versionNumber = TitleDataFieldsDescriptionGetValue( versionField, this ); ConVarRef cl_configversion("cl_configversion"); // Check the version number to see if this is a new save profile. // In that case, we need to reset everything to the defaults. if ( versionNumber != cl_configversion.GetInt() ) { // This will wipe out all achievement and stats. This is called in Host_ResetConfiguration for xbox, but we don't // want to call it for anything that uses steam unless we're okay with clearning all stats. IGameEvent *event = g_pMatchExtensions->GetIGameEventManager2()->CreateEvent( "reset_game_titledata" ); if ( event ) { event->SetInt( "controllerId", m_iController ); g_pMatchExtensions->GetIGameEventManager2()->FireEventClientSide( event ); } // ResetConfiguration will set all the settings to the defaults. g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "ResetConfiguration", "iController", m_iController ) ); } #endif #if !defined ( _X360 ) // send an event to anyone else who needs Steam user stat data IGameEvent *event = g_pMatchExtensions->GetIGameEventManager2()->CreateEvent( "user_data_downloaded" ); if ( event ) { #ifdef GAME_DLL g_pMatchExtensions->GetIGameEventManager2()->FireEvent( event ); #else // not sure this event is caught anywhere but we brought it over from orange box just in case g_pMatchExtensions->GetIGameEventManager2()->FireEventClientSide( event ); #endif } #endif // After we loaded some title data, see if we need to retrospectively award achievements EvaluateAwardsStateBasedOnStats(); // Flush stats if we are clearing for debugging if ( mm_cfgoverride_commit.GetBool() ) steamapicontext->SteamUserStats()->StoreStats(); // // Finished reading stats // DevMsg( "User%d stats retrieved.\n", m_iController ); OnProfileTitleDataLoaded( 0 ); #ifdef _DEBUG static bool debugDumpStats = true; if ( debugDumpStats ) { debugDumpStats = false; // Debug code. // Dump the stats once loaded so we can see what they are. g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "ms_player_dump_properties" ); } #endif } void PlayerLocal::Steam_OnPersonaStateChange( PersonaStateChange_t *pParam ) { if ( !steamapicontext || !steamapicontext->SteamUtils() || !steamapicontext->SteamFriends() || !steamapicontext->SteamUser() || !pParam || !m_xuid ) return; // Check that something changed about local user if ( m_xuid == pParam->m_ulSteamID ) { if ( pParam->m_nChangeFlags & k_EPersonaChangeName ) { CSteamID steamID; steamID.SetFromUint64( m_xuid ); if ( char const *szName = steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ) ) { Q_strncpy( m_szName, szName, ARRAYSIZE( m_szName ) ); } if ( cl_names_debug.GetBool() ) { Q_strncpy( m_szName, PLAYER_DEBUG_NAME, ARRAYSIZE( m_szName ) ); } } } } void PlayerLocal::UpdatePlayersSteamLogon() { if ( !steamapicontext->SteamUser() ) return; IPlayer::OnlineState_t eState = steamapicontext->SteamUser()->BLoggedOn() ? IPlayer::STATE_ONLINE : IPlayer::STATE_OFFLINE; m_eOnlineState = eState; // Update XUID on PS3: CSteamID cSteamId = steamapicontext->SteamUser()->GetSteamID(); if ( !m_xuid && cSteamId.IsValid() ) { m_xuid = steamapicontext->SteamUser()->GetSteamID().ConvertToUint64(); } } void PlayerLocal::Steam_OnServersConnected( SteamServersConnected_t *pParam ) { DevMsg( "Steam_OnServersConnected\n" ); UpdatePlayersSteamLogon(); } void PlayerLocal::Steam_OnServersDisconnected( SteamServersDisconnected_t *pParam ) { DevWarning( "Steam_OnServersDisconnected\n" ); UpdatePlayersSteamLogon(); } #endif void PlayerLocal::SetTitleDataWriteTime( float flTime ) { m_flLastSave = flTime; } #if defined ( _X360 ) bool PlayerLocal::IsTitleDataBlockValid( int blockId ) { if ( blockId < 0 || blockId >= TITLE_DATA_COUNT ) return false; return m_bIsTitleDataBlockValid[ blockId ]; } void PlayerLocal::ClearBufTitleData( void ) { memset( m_bufTitleData, 0, sizeof( m_bufTitleData ) ); } #endif // Test if we can still read from profile; used when storage device is removed and we // want to verify the profile still has a storage connection bool PlayerLocal::IsTitleDataStorageConnected( void ) { #if defined( _X360 ) // try to write out storage block 3 to see if there is a storage unit associated with this profile CUtlVector< XUSER_PROFILE_SETTING > pXPS; DWORD dwNumDataBufferBytes = XPROFILE_SETTING_MAX_SIZE; CArrayAutoPtr< char > spDataBuffer( new char[ dwNumDataBufferBytes ] ); V_memset( spDataBuffer.Get(), 0, dwNumDataBufferBytes ); int titleStorageBlock3Index = 2; XUSER_PROFILE_SETTING xps; V_memset( &xps, 0, sizeof( xps ) ); xps.dwSettingId = GetTitleSpecificDataId( titleStorageBlock3Index ); xps.data.type = XUSER_DATA_TYPE_BINARY; xps.data.binary.cbData = XPROFILE_SETTING_MAX_SIZE; xps.data.binary.pbData = (PBYTE) spDataBuffer.Get(); V_memcpy( xps.data.binary.pbData, m_bufTitleData[ titleStorageBlock3Index ], XPROFILE_SETTING_MAX_SIZE ); pXPS.AddToTail( xps ); // // Issue the XWrite operation // DWORD ret; ret = g_pMatchExtensions->GetIXOnline()->XUserWriteProfileSettings( m_iController, pXPS.Count(), pXPS.Base(), NULL ); if ( ret != ERROR_SUCCESS ) { return false; } #endif return true; } void PlayerLocal::WriteTitleData() { #if defined( _DEMO ) && defined( _X360 ) // Demo versions are not allowed to write profile data return; #endif #ifdef _X360 if ( !m_xuid ) return; if ( GetAssumedSigninState() == eXUserSigninState_NotSignedIn ) return; #if defined( CSTRIKE15 ) // Code to handle TCR 047 // Calling GetXWriteOpportunity clears the MM_XWriteOppurtinty to MMXWO_NONE // but we don't want that if we are trying to write within 3 seconds of the last // write. Rather we want the write to succeed after the 3 seconds expire; so // we just bail until the 3 seconds is up and then allow the write to happen // We do not have to queue up writes since the data we store on 360 is // live data and is always the most current version of the data const float cMinWriteDelay = 3.0f; if ( Plat_FloatTime() - m_flLastSave < cMinWriteDelay ) { return; } #endif // NOTE: Need to call this here, because this has side effects. // Getting the opportunity will clear the opportunity. This is used // to only allow writes to happen at times out of game where we // can be sure we don't hitch. If we don't do it here, then we can get into // a state where some previous opportunity to write was set (say, // leaving a cooperative game) with no stats to be saved. If that happens // and we don't reset the state, then the next time we enter a new level // it'll save at a bad time. MM_XWriteOpportunity eXWO = GetXWriteOpportunity( m_iController ); #endif // // Determine if XWrite is required first // DWORD numXWritesRequired = 0; for ( int iData = 0; iData < ( IsX360() ? TITLE_DATA_COUNT_X360 : TITLE_DATA_COUNT ); ++ iData ) { if ( !m_bSaveTitleData[iData] ) continue; ++ numXWritesRequired; } if ( !numXWritesRequired ) // early out if nothing to do here return; #ifdef _X360 if ( m_eLoadedTitleData < GetAssumedSigninState() ) { // haven't loaded data for the state return; } if ( !IsTitleDataValid() ) return; bool bCanXWrite = true; #if !defined (CSTRIKE15 ) // // Check if we can actually XWrite (TCR 136) // static const float s_flXWritesFreq = 5 * 60 + 1; // 5 minutes if ( Plat_FloatTime() - m_flLastSave < s_flXWritesFreq ) bCanXWrite = false; switch ( eXWO ) { default: case MMXWO_NONE: bCanXWrite = false; break; case MMXWO_CHECKPOINT: break; case MMXWO_SETTINGS: case MMXWO_SESSION_FINISHED: bCanXWrite = true; break; } #else // Cstrike only writes to user profile; writes are <500ms; earlier code ensures we only // write to profile at most every 3 seconds so we are TCR compliant // Cstrike needs a waiver for every 5 minute writes since we save stats at end of round // so we can ignore 5 minute timer check; // If we get to this code for any WriteOpportunity we are ok to write if ( eXWO == MMXWO_NONE ) { bCanXWrite = false; } #endif if ( !bCanXWrite ) { // have to wait longer return; } // // Prepare the XWrite batch // float flTimeStart; flTimeStart = Plat_FloatTime(); Msg( "Player %d : WriteTitleData initiated...\n", m_iController ); CUtlVector< XUSER_PROFILE_SETTING > pXPS; DWORD dwNumDataBufferBytes = TITLE_DATA_COUNT_X360 * XPROFILE_SETTING_MAX_SIZE; CArrayAutoPtr< char > spDataBuffer( new char[ dwNumDataBufferBytes ] ); V_memset( spDataBuffer.Get(), 0, dwNumDataBufferBytes ); for ( int iData = 0; iData < TITLE_DATA_COUNT_X360; ++ iData ) { if ( !m_bSaveTitleData[iData] ) continue; Msg( "Player %d : WriteTitleData preparing TitleData%d...\n", m_iController, iData + 1 ); XUSER_PROFILE_SETTING xps; V_memset( &xps, 0, sizeof( xps ) ); xps.dwSettingId = GetTitleSpecificDataId( iData ); xps.data.type = XUSER_DATA_TYPE_BINARY; xps.data.binary.cbData = XPROFILE_SETTING_MAX_SIZE; xps.data.binary.pbData = (PBYTE) spDataBuffer.Get() + iData * XPROFILE_SETTING_MAX_SIZE; V_memcpy( xps.data.binary.pbData, m_bufTitleData[ iData ], XPROFILE_SETTING_MAX_SIZE ); pXPS.AddToTail( xps ); } // Clear dirty state V_memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData[0] ) * TITLE_DATA_COUNT_X360 ); // // Issue the XWrite operation // DWORD ret; m_flLastSave = Plat_FloatTime(); ret = g_pMatchExtensions->GetIXOnline()->XUserWriteProfileSettings( m_iController, pXPS.Count(), pXPS.Base(), NULL ); if ( ret != ERROR_SUCCESS ) { Warning( "Player %d : WriteTitleData failed (%.3f sec), err=0x%08X\n", m_iController, Plat_FloatTime() - flTimeStart, ret ); g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataWriteFailed", "iController", m_iController ) ); g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileUnavailable", "iController", m_iController ) ); } else { Msg( "Player %d : WriteTitleData finished (%.3f sec).\n", m_iController, Plat_FloatTime() - flTimeStart ); } #elif !defined( NO_STEAM ) // // Steam stats have been written earlier // Clear dirty state // V_memset( m_bSaveTitleData, 0, sizeof( m_bSaveTitleData ) ); g_pPlayerManager->RequestStoreStats(); GetXWriteOpportunity( m_iController ); // clear XWrite opportunity DevMsg( "User stats written.\n" ); #endif g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataSaved", "iController", m_iController ) ); } void PlayerLocal::Update() { #ifdef _X360 // When we are playing as guest, no updates if ( !m_xuid ) return; UpdatePendingAwardsState(); #endif // Load title data if not loaded yet LoadTitleData(); // Save title data if time has come to do so WriteTitleData(); #ifndef NO_STEAM // Re-request only if we got our callback with an error code. if ( s_flSteamStatsRequestTime && ( Plat_FloatTime() - s_flSteamStatsRequestTime > 10.0 ) && s_bSteamStatsRequestFailed ) { DevWarning( "=========== Failed to retrieve Steam stats (%2.2f sec) =================\n", Plat_FloatTime() - s_flSteamStatsRequestTime ); steamapicontext->SteamUserStats()->RequestCurrentStats(); s_flSteamStatsRequestTime = Plat_FloatTime(); } #endif } void PlayerLocal::Destroy() { m_iController = XUSER_INDEX_NONE; m_xuid = 0; m_eOnlineState = STATE_OFFLINE; #ifdef _X360 #elif !defined( NO_STEAM ) m_CallbackOnUserStatsReceived.Unregister(); m_CallbackOnPersonaStateChange.Unregister(); #endif delete this; } void PlayerLocal::RecomputeXUID( char const *szNetwork ) { if ( !m_xuid ) return; #ifdef _X360 DWORD dwFlagSignin = XUSER_GET_SIGNIN_INFO_OFFLINE_XUID_ONLY; if ( !Q_stricmp( "LIVE", szNetwork ) ) dwFlagSignin = XUSER_GET_SIGNIN_INFO_ONLINE_XUID_ONLY; XUSER_SIGNIN_INFO xsi; if ( ERROR_SUCCESS != XUserGetSigninInfo( m_iController, dwFlagSignin, &xsi ) || !xsi.xuid ) { if ( ERROR_SUCCESS != XUserGetXUID( m_iController, &xsi.xuid ) ) { DevWarning( "Player::RecomputeXUID failed! Leaving ctrlr%d as %llx.\n", m_iController, m_xuid ); } else { m_xuid = xsi.xuid; } } else { m_xuid = xsi.xuid; } #endif } void PlayerLocal::DetectOnlineState() { #ifdef _X360 OnlineState_t eOnlineState = IPlayer::STATE_OFFLINE; if ( !XBX_GetPrimaryUserIsGuest() ) { if ( XUserGetSigninState( m_iController ) == eXUserSigninState_SignedInToLive ) { eOnlineState = IPlayer::STATE_NO_MULTIPLAYER; BOOL bValue = false; if ( ERROR_SUCCESS == XUserCheckPrivilege( m_iController, XPRIVILEGE_MULTIPLAYER_SESSIONS, &bValue ) && bValue ) eOnlineState = IPlayer::STATE_ONLINE; } } m_eOnlineState = eOnlineState; #endif } const UserProfileData & PlayerLocal::GetPlayerProfileData() { return m_ProfileData; } void PlayerLocal::LoadPlayerProfileData() { #ifdef _X360 // These are the values we're interested in having returned (must match the indices above) const DWORD dwSettingIds[] = { XPROFILE_GAMERCARD_REP, XPROFILE_GAMER_DIFFICULTY, XPROFILE_GAMER_CONTROL_SENSITIVITY, XPROFILE_GAMER_YAXIS_INVERSION, XPROFILE_OPTION_CONTROLLER_VIBRATION, XPROFILE_GAMER_PREFERRED_COLOR_FIRST, XPROFILE_GAMER_PREFERRED_COLOR_SECOND, XPROFILE_GAMER_ACTION_AUTO_AIM, XPROFILE_GAMER_ACTION_AUTO_CENTER, XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL, XPROFILE_GAMERCARD_REGION, XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED , XPROFILE_GAMERCARD_CRED, XPROFILE_GAMERCARD_ZONE, XPROFILE_GAMERCARD_TITLES_PLAYED, XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED, XPROFILE_GAMERCARD_TITLE_CRED_EARNED, // [jason] For debugging voice settings only XPROFILE_OPTION_VOICE_MUTED, XPROFILE_OPTION_VOICE_THRU_SPEAKERS, XPROFILE_OPTION_VOICE_VOLUME }; enum { NUM_PROFILE_SETTINGS = ARRAYSIZE( dwSettingIds ) }; // First, we call with a NULL pointer and zero size to retrieve the buffer size we'll get back DWORD dwResultSize = 0; // Must be zero to get the correct size back XUSER_READ_PROFILE_SETTING_RESULT *pResults = NULL; DWORD dwError = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings( 0, // Family ID (current title) m_iController, NUM_PROFILE_SETTINGS, dwSettingIds, &dwResultSize, pResults, NULL ); // We need this to inform us that it's given us a size back for the buffer if ( dwError != ERROR_INSUFFICIENT_BUFFER ) { Warning( "Player %d : LoadPlayerProfileData failed to get size (err=0x%08X)!\n", m_iController, dwError ); return; } // Now we allocate that buffer and supply it to the call BYTE *pData = (BYTE *) stackalloc( dwResultSize ); ZeroMemory( pData, dwResultSize ); pResults = (XUSER_READ_PROFILE_SETTING_RESULT *) pData; dwError = g_pMatchExtensions->GetIXOnline()->XUserReadProfileSettings( 0, // Family ID (current title) m_iController, NUM_PROFILE_SETTINGS, dwSettingIds, &dwResultSize, pResults, NULL ); // Not overlapped, must be synchronous // We now have a raw buffer of results if ( dwError != ERROR_SUCCESS ) { Warning( "Player %d : LoadTitleData failed to get data (err=0x%08X)!\n", m_iController, dwError ); return; } for ( DWORD k = 0; k < pResults->dwSettingsLen; ++ k ) { XUSER_PROFILE_SETTING const &xps = pResults->pSettings[k]; switch ( xps.dwSettingId ) { case XPROFILE_GAMERCARD_REP: m_ProfileData.reputation = xps.data.fData; break; case XPROFILE_GAMER_DIFFICULTY: m_ProfileData.difficulty = xps.data.nData; break; case XPROFILE_GAMER_CONTROL_SENSITIVITY: m_ProfileData.sensitivity = xps.data.nData; break; case XPROFILE_GAMER_YAXIS_INVERSION: m_ProfileData.yaxis = xps.data.nData; break; case XPROFILE_OPTION_CONTROLLER_VIBRATION: m_ProfileData.vibration = xps.data.nData; break; case XPROFILE_GAMER_PREFERRED_COLOR_FIRST: m_ProfileData.color1 = xps.data.nData; break; case XPROFILE_GAMER_PREFERRED_COLOR_SECOND: m_ProfileData.color2 = xps.data.nData; break; case XPROFILE_GAMER_ACTION_AUTO_AIM: m_ProfileData.action_autoaim = xps.data.nData; break; case XPROFILE_GAMER_ACTION_AUTO_CENTER: m_ProfileData.action_autocenter = xps.data.nData; break; case XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL: m_ProfileData.action_movementcontrol = xps.data.nData; break; case XPROFILE_GAMERCARD_REGION: m_ProfileData.region = xps.data.nData; break; case XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED: m_ProfileData.achearned = xps.data.nData; break; case XPROFILE_GAMERCARD_CRED: m_ProfileData.cred = xps.data.nData; break; case XPROFILE_GAMERCARD_ZONE: m_ProfileData.zone = xps.data.nData; break; case XPROFILE_GAMERCARD_TITLES_PLAYED: m_ProfileData.titlesplayed = xps.data.nData; break; case XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED: m_ProfileData.titleachearned = xps.data.nData; break; case XPROFILE_GAMERCARD_TITLE_CRED_EARNED: m_ProfileData.titlecred = xps.data.nData; break; // [jason] For debugging voice settings only: case XPROFILE_OPTION_VOICE_MUTED: DevMsg( "Player %d : XPROFILE_OPTION_VOICE_MUTED setting: %d\n", m_iController, xps.data.nData ); break; case XPROFILE_OPTION_VOICE_THRU_SPEAKERS: DevMsg( "Player %d : XPROFILE_OPTION_VOICE_THRU_SPEAKERS setting: %d\n", m_iController, xps.data.nData ); break; case XPROFILE_OPTION_VOICE_VOLUME: DevMsg( "Player %d : XPROFILE_OPTION_VOICE_VOLUME setting: %d\n", m_iController, xps.data.nData ); break; } } #endif #ifdef _PS3 m_ProfileData.vibration = 3; // vibration enabled on PS3 #endif DevMsg( "Player %d : LoadPlayerProfileData finished\n", m_iController ); } MatchmakingData* PlayerLocal::GetPlayerMatchmakingData( void ) { return &m_MatchmakingData; } void PlayerLocal::UpdatePlayerMatchmakingData( int mmDataType ) { #if defined ( _X360 ) if ( mmDataType < 0 || mmDataType >= MMDATA_TYPE_COUNT ) { DevMsg( "Invalid matchmaking data type passed to UpdatePlayerMatchmakingData ( %d )", mmDataType ); return; } DevMsg( "Player::UpdatePlayerMatchmakingData( ctrlr%d; mmDataType%d )\n", GetPlayerIndex(), mmDataType ); // Now obtain the avg calculator for the difficulty // Calculator operates on "avgValue" and "newValue" CExpressionCalculator calc( g_pMMF->GetMatchTitleGameSettingsMgr()->GetFormulaAverage( mmDataType ) ); char const *szAvg = "avgValue"; char const *szNew = "newValue"; float flResult; // // Average up our data // #define CALC_AVG( field ) \ calc.SetVariable( szAvg, m_MatchmakingData.field[mmDataType][MMDATA_SCOPE_LIFETIME] ); \ calc.SetVariable( szNew, m_MatchmakingData.field[mmDataType][MMDATA_SCOPE_ROUND] * MM_AVG_CONST ); \ if ( calc.Evaluate( flResult ) ) \ m_MatchmakingData.field[mmDataType][MMDATA_SCOPE_LIFETIME] = flResult; CALC_AVG( mContribution ); CALC_AVG( mMVPs ); CALC_AVG( mKills ); CALC_AVG( mDeaths ); CALC_AVG( mHeadShots ); CALC_AVG( mDamage ); CALC_AVG( mShotsFired ); CALC_AVG( mShotsHit ); CALC_AVG( mDominations ); // Average rounds played makes no sense since the average is a per round average; increment both the average and running totals m_MatchmakingData.mRoundsPlayed[mmDataType][MMDATA_SCOPE_LIFETIME] += 1; m_MatchmakingData.mRoundsPlayed[mmDataType][MMDATA_SCOPE_ROUND] += 1; #undef CALC_AVG #endif // #if defined ( _X360 ) } void PlayerLocal::ResetPlayerMatchmakingData( int mmDataScope ) { #if defined ( _X360 ) if ( mmDataScope < 0 || mmDataScope >= MMDATA_SCOPE_COUNT ) { DevMsg( "Invalid matchmaking data scope passed to ResetPlayerMatchmakingData ( %d )", mmDataScope ); return; } DevMsg( "Player::ResetPlayerMatchmakingData( ctrlr%d; mmDataScope%d )\n", GetPlayerIndex(), mmDataScope ); ConVarRef score_default( "score_default" ); for ( int i=0; i= 0 && iTitleDataIndex < TITLE_DATA_COUNT ) { return m_bufTitleData[ iTitleDataIndex ]; } else { return NULL; } } void PlayerLocal::UpdatePlayerTitleData( TitleDataFieldsDescription_t const *fdKey, const void *pvNewTitleData, int numNewBytes ) { if ( !fdKey || (fdKey->m_eDataType == fdKey->DT_0) || !pvNewTitleData || (numNewBytes <= 0) || !fdKey->m_szFieldName ) return; Assert( TITLE_DATA_COUNT == TitleDataFieldsDescription_t::DB_TD_COUNT ); if ( fdKey->m_iTitleDataBlock < 0 || fdKey->m_iTitleDataBlock >= TitleDataFieldsDescription_t::DB_TD_COUNT ) return; #if !defined( NO_STEAM ) && !defined( _CERT ) if ( steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->GetConnectedUniverse() == k_EUniverseBeta ) #endif { #if ( defined( _GAMECONSOLE ) && !defined( _CERT ) ) // Validate that the caller is not forging the field description bool bKeyForged = true; for ( TitleDataFieldsDescription_t const *fdCheck = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(); fdCheck && fdCheck->m_szFieldName; ++ fdCheck ) { if ( fdCheck == fdKey ) { bKeyForged = false; break; } } if ( bKeyForged ) { DevWarning( "PlayerLocal::UpdatePlayerTitleData( %s ) with invalid key!\n", fdKey->m_szFieldName ); Assert( 0 ); return; } #endif } // Validate data size if ( fdKey->m_eDataType/8 != numNewBytes ) { DevWarning( "PlayerLocal::UpdatePlayerTitleData( %s ) new data size %d != description size %d!\n", fdKey->m_szFieldName, numNewBytes, fdKey->m_eDataType/8 ); Assert( 0 ); return; } // Debug output for the write attempt switch ( fdKey->m_eDataType ) { case TitleDataFieldsDescription_t::DT_uint8: case TitleDataFieldsDescription_t::DT_BITFIELD: // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %d (0x%02X)\n", GetName(), fdKey->m_szFieldName, *( uint8 const * ) pvNewTitleData, *( uint8 const * ) pvNewTitleData ); break; case TitleDataFieldsDescription_t::DT_uint16: // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %d (0x%04X)\n", GetName(), fdKey->m_szFieldName, *( uint16 const * ) pvNewTitleData, *( uint16 const * ) pvNewTitleData ); break; case TitleDataFieldsDescription_t::DT_uint32: // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %d (0x%08X)\n", GetName(), fdKey->m_szFieldName, *( uint32 const * ) pvNewTitleData, *( uint32 const * ) pvNewTitleData ); break; case TitleDataFieldsDescription_t::DT_uint64: // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %llu (0x%llX)\n", GetName(), fdKey->m_szFieldName, *( uint64 const * ) pvNewTitleData, *( uint64 const * ) pvNewTitleData ); break; case TitleDataFieldsDescription_t::DT_float: // DevMsg( "PlayerLocal(%s)::UpdatePlayerTitleData: %s = %f (%0.2f)\n", GetName(), fdKey->m_szFieldName, *( float const * ) pvNewTitleData, *( float const * ) pvNewTitleData ); break; } // Check if the title allows the requested stat update KeyValues *kvTitleRequest = new KeyValues( "stat" ); KeyValues::AutoDelete autodelete_kvTitleRequest( kvTitleRequest ); kvTitleRequest->SetString( "name", fdKey->m_szFieldName ); if ( !g_pMMF->GetMatchTitleGameSettingsMgr()->AllowClientProfileUpdate( kvTitleRequest ) ) return; // // Copy off the old data and check that the new data is different // At the same time update internal buffers with new data // uint8 *pvData = ( uint8 * ) m_bufTitleData[ fdKey->m_iTitleDataBlock ]; switch ( fdKey->m_eDataType ) { case TitleDataFieldsDescription_t::DT_BITFIELD: { uint8 *pvOldData = ( uint8 * ) stackalloc( numNewBytes ); uint8 bBitValue = !!*( ( uint8 const * ) pvNewTitleData ); uint8 bBitMask = ( 1 << (fdKey->m_numBytesOffset%8) ); memcpy( pvOldData, pvData + fdKey->m_numBytesOffset/8, numNewBytes ); if ( !!( bBitMask & pvOldData[0] ) == bBitValue ) return; if ( bBitValue ) pvOldData[0] |= bBitMask; else pvOldData[0] &=~bBitMask; memcpy( pvData + fdKey->m_numBytesOffset/8, pvOldData, numNewBytes ); } break; default: if ( !memcmp( pvData + fdKey->m_numBytesOffset, pvNewTitleData, numNewBytes ) ) return; memcpy( pvData + fdKey->m_numBytesOffset, pvNewTitleData, numNewBytes ); break; } // Check our "guest" status #ifdef _GAMECONSOLE bool bRegisteredPlayer = false; for ( int k = 0; k < XBX_GetNumGameUsers(); ++ k ) { if ( XBX_GetUserId( k ) == m_iController ) { if ( XBX_GetUserIsGuest( k ) ) { DevMsg( "pPlayerLocal(%s)->UpdatePlayerTitleData not saving for guests.\n", GetName() ); return; } bRegisteredPlayer = true; break; } } if ( !bRegisteredPlayer ) { DevMsg( "pPlayerLocal(%s)->UpdatePlayerTitleData not saving for not participating gamers.\n", GetName() ); Assert( 0 ); // title code shouldn't be calling UpdateAwardsData for players not in active gameplay, title bug? return; } #endif // Mark stats to be stored at next available opportunity m_bSaveTitleData[ fdKey->m_iTitleDataBlock ] = true; #ifndef NO_STEAM // On Steam we can freely upload stats rights now // // Prepare the data // switch( fdKey->m_eDataType ) { case TitleDataFieldsDescription_t::DT_uint8: case TitleDataFieldsDescription_t::DT_uint16: case TitleDataFieldsDescription_t::DT_uint32: { uint32 i32field[4] = {0}; memcpy( &i32field[3], &m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset ], fdKey->m_eDataType / 8 ); i32field[0] = *( uint32 * )( &i32field[3] ); i32field[1] = *( uint16 * )( &i32field[3] ); i32field[2] = *( uint8 * )( &i32field[3] ); SetSteamStatWithPotentialOverride( fdKey->m_szFieldName, ( int32 ) i32field[ 2 - ( fdKey->m_eDataType / 16 ) ] ); } break; case TitleDataFieldsDescription_t::DT_float: { float flField = 0.0f; memcpy( &flField, &m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset ], fdKey->m_eDataType / 8 ); SetSteamStatWithPotentialOverride( fdKey->m_szFieldName, flField ); } break; case TitleDataFieldsDescription_t::DT_uint64: { uint32 i32field[2] = { 0 }; memcpy( &i32field[0], &m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset ], fdKey->m_eDataType / 8 ); char chBuffer[ 256 ] = {0}; for ( int k = 0; k < ARRAYSIZE( i32field ); ++ k ) { Q_snprintf( chBuffer, ARRAYSIZE( chBuffer ), "%s.%d", fdKey->m_szFieldName, k ); SetSteamStatWithPotentialOverride( chBuffer, ( int32 ) i32field[k] ); } } break; case TitleDataFieldsDescription_t::DT_BITFIELD: { #ifdef STEAM_PACK_BITFIELDS char chStatField[64] = {0}; uint32 uiOffsetInTermsOfUINT32 = fdKey->m_numBytesOffset/32; V_snprintf( chStatField, sizeof( chStatField ), "bitfield_%02u_%03X", fdKey->m_iTitleDataBlock + 1, uiOffsetInTermsOfUINT32*4 ); int32 iCombinedBitValue = ( reinterpret_cast< uint32 * >( &m_bufTitleData[fdKey->m_iTitleDataBlock][0] ) )[ uiOffsetInTermsOfUINT32 ]; SetSteamStatWithPotentialOverride( chStatField, iCombinedBitValue ); #else int32 i32field = !!( m_bufTitleData[ fdKey->m_iTitleDataBlock ][ fdKey->m_numBytesOffset/8 ] & ( 1 << ( fdKey->m_numBytesOffset % 8 ) ) ); SetSteamStatWithPotentialOverride( fdKey->m_szFieldName, i32field ); #endif } break; } #endif // NO_STEAM // If a component achievement was affected, evaluate its state int numFieldNameChars = Q_strlen( fdKey->m_szFieldName ); if ( ( numFieldNameChars > 0 ) && ( fdKey->m_szFieldName[numFieldNameChars - 1] == ']' ) ) { EvaluateAwardsStateBasedOnStats(); } } void PlayerLocal::GetLeaderboardData( KeyValues *pLeaderboardInfo ) { // Iterate over all views specified for ( KeyValues *pView = pLeaderboardInfo->GetFirstTrueSubKey(); pView; pView = pView->GetNextTrueSubKey() ) { char const *szViewName = pView->GetName(); KeyValues *pCurrentData = m_pLeaderboardData->FindKey( szViewName ); // If no such data yet or refresh was requested, then queue this request if ( pView->GetInt( ":refresh" ) || !pCurrentData ) { if ( g_pLeaderboardRequestQueue ) g_pLeaderboardRequestQueue->Request( pView ); } // If we have data, then fill in the values pView->MergeFrom( pCurrentData, KeyValues::MERGE_KV_BORROW ); } } void PlayerLocal::UpdateLeaderboardData( KeyValues *pLeaderboardInfo ) { DevMsg( "PlayerLocal::UpdateLeaderboardData for %s ...\n", GetName() ); #ifdef _X360 IX360LeaderboardBatchWriter *pLbWriter = MMX360_CreateLeaderboardBatchWriter( m_xuid ); #endif // Iterate over all views specified for ( KeyValues *pView = pLeaderboardInfo->GetFirstTrueSubKey(); pView; pView = pView->GetNextTrueSubKey() ) { char const *szViewName = pView->GetName(); // Check if the title allows the requested leaderboard update KeyValues *kvTitleRequest = new KeyValues( "leaderboard" ); KeyValues::AutoDelete autodelete_kvTitleRequest( kvTitleRequest ); kvTitleRequest->SetString( "name", szViewName ); if ( !g_pMMF->GetMatchTitleGameSettingsMgr()->AllowClientProfileUpdate( kvTitleRequest ) ) continue; // Find leaderboard description KeyValues *pDescription = g_pMMF->GetMatchTitle()->DescribeTitleLeaderboard( szViewName ); if ( !pDescription ) { DevWarning( " View %s failed to allocate description!\n", szViewName ); } KeyValues::AutoDelete autodelete_pDescription( pDescription ); KeyValues *pCurrentData = m_pLeaderboardData->FindKey( szViewName ); if ( !pDescription->GetBool( ":nocache" ) && !pCurrentData ) { pCurrentData = new KeyValues( szViewName ); m_pLeaderboardData->AddSubKey( pCurrentData ); } // On PC we just issue a write per each request, no batching if ( !IsX360() ) { if ( pDescription ) { #if !defined( _X360 ) && !defined( NO_STEAM ) Steam_WriteLeaderboardData( pDescription, pView ); #endif } continue; } // Check if the rating field passes rule requirement if ( KeyValues *pValDesc = pDescription->FindKey( ":rating" ) ) { char const *szValue = pDescription->GetString( ":rating/name" ); KeyValues *val = pView->FindKey( szValue ); if ( !val || !*szValue ) { DevWarning( " View %s is rated, but no :rating field '%s' in update!\n", szViewName, szValue ); continue; } char const *szRule = pValDesc->GetString( "rule" ); IPropertyRule *pRuleObj = GetRuleByName( szRule ); if ( !pRuleObj ) { DevWarning( " View %s is rated, but invalid rule '%s' specified!\n", szViewName, szRule ); continue; } uint64 uiValue = val->GetUint64(); uint64 uiCurrent = pCurrentData->GetUint64( szValue ); if ( !pRuleObj->ApplyRuleUint64( uiCurrent, uiValue ) ) { DevMsg( " View %s is rated, rejected by rule '%s' ( old %lld, new %lld )!\n", szViewName, szRule, uiCurrent, val->GetUint64() ); continue; } DevMsg( " View %s is rated, rule '%s' passed ( old %lld, new %lld )!\n", szViewName, szRule, uiCurrent, uiValue ); pCurrentData->SetUint64( ":rating", uiValue ); // ... and proceed setting other contexts and properties } // Iterate over all values for ( KeyValues *val = pView->GetFirstValue(); val; val = val->GetNextValue() ) { char const *szValue = val->GetName(); if ( szValue[0] == ':' ) continue; // no update for system fields if ( KeyValues *pValDesc = pDescription->FindKey( szValue ) ) { char const *szRule = pValDesc->GetString( "rule" ); IPropertyRule *pRuleObj = GetRuleByName( szRule ); if ( !pRuleObj ) { DevWarning( " View %s/%s has invalid rule '%s' specified!\n", szViewName, szValue, szRule ); continue; } char const *szType = pValDesc->GetString( "type" ); if ( !Q_stricmp( "uint64", szType ) ) { uint64 uiValue = val->GetUint64(); uint64 uiCurrent = pCurrentData->GetUint64( szValue ); // Check if new value passes the rule if ( !pRuleObj->ApplyRuleUint64( uiCurrent, uiValue ) ) { DevMsg( " View %s/%s rejected by rule '%s' ( old %lld, new %lld )!\n", szViewName, szValue, szRule, uiCurrent, val->GetUint64() ); continue; } DevMsg( " View %s/%s updated by rule '%s' ( old %lld, new %lld )!\n", szViewName, szValue, szRule, uiCurrent, uiValue ); pCurrentData->SetUint64( szValue, uiValue ); #ifdef _X360 if ( pLbWriter ) { XUSER_PROPERTY xProp = {0}; xProp.dwPropertyId = pValDesc->GetInt( "prop" ); xProp.value.type = XUSER_DATA_TYPE_INT64; xProp.value.i64Data = uiValue; pLbWriter->AddProperty( pDescription->GetInt( ":id" ), xProp ); } #endif } else if ( !Q_stricmp( "float", szType ) ) { float flValue = val->GetFloat(); float flCurrent = pCurrentData->GetFloat( szValue ); // Check if new value passes the rule if ( !pRuleObj->ApplyRuleFloat( flCurrent, flValue ) ) { DevMsg( " View %s/%s rejected by rule '%s' ( old %.4f, new %.4f )!\n", szViewName, szValue, szRule, flCurrent, val->GetFloat() ); continue; } DevMsg( " View %s/%s updated by rule '%s' ( old %.4f, new %.4f )!\n", szViewName, szValue, szRule, flCurrent, flValue ); pCurrentData->SetFloat( szValue, flValue ); #ifdef _X360 if ( pLbWriter ) { XUSER_PROPERTY xProp = {0}; xProp.dwPropertyId = pValDesc->GetInt( "prop" ); xProp.value.type = XUSER_DATA_TYPE_FLOAT; xProp.value.i64Data = flValue; pLbWriter->AddProperty( pDescription->GetInt( ":id" ), xProp ); } #endif } else { DevWarning( " View %s/%s has invalid type '%s' specified!\n", szViewName, szValue, szType ); } } else { DevWarning( " View %s/%s is missing description!\n", szViewName, szValue ); } } } // // Now submit all accumulated leaderboard writes // #ifdef _X360 if ( pLbWriter ) { pLbWriter->WriteBatchAndDestroy(); pLbWriter = NULL; } else { Warning( "PlayerLocal::UpdateLeaderboardData failed to save leaderboard data to writer!\n" ); Assert( 0 ); } #endif DevMsg( "PlayerLocal::UpdateLeaderboardData for %s finished.\n", GetName() ); } void PlayerLocal::OnLeaderboardRequestFinished( KeyValues *pLeaderboardData ) { m_pLeaderboardData->MergeFrom( pLeaderboardData, KeyValues::MERGE_KV_UPDATE ); KeyValues *kvEvent = new KeyValues( "OnProfileLeaderboardData" ); kvEvent->SetInt( "iController", m_iController ); kvEvent->MergeFrom( pLeaderboardData, KeyValues::MERGE_KV_UPDATE ); g_pMatchEventsSubscription->BroadcastEvent( kvEvent ); } void PlayerLocal::GetAwardsData( KeyValues *pAwardsData ) { for ( KeyValues *kvValue = pAwardsData->GetFirstValue(); kvValue; kvValue = kvValue->GetNextValue() ) { char const *szName = kvValue->GetName(); // If title is requesting all achievement IDs if ( !Q_stricmp( "@achievements", szName ) ) { for ( int k = 0; k < m_arrAchievementsEarned.Count(); ++ k ) { KeyValues *kvAchValue = new KeyValues( "" ); kvAchValue->SetInt( NULL, m_arrAchievementsEarned[k] ); kvValue->AddSubKey( kvAchValue ); } continue; } // If title is requesting all awards IDs if ( !Q_stricmp( "@awards", szName ) ) { for ( int k = 0; k < m_arrAvatarAwardsEarned.Count(); ++ k ) { KeyValues *kvAchValue = new KeyValues( "" ); kvAchValue->SetInt( NULL, m_arrAvatarAwardsEarned[k] ); kvValue->AddSubKey( kvAchValue ); } continue; } // Try to find the achievement in the achievement map for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements(); pAchievement && pAchievement->m_szAchievementName; ++ pAchievement ) { if ( !Q_stricmp( szName, pAchievement->m_szAchievementName ) ) { kvValue->SetInt( "", ( m_arrAchievementsEarned.Find( pAchievement->m_idAchievement ) != m_arrAchievementsEarned.InvalidIndex() ) ? 1 : 0 ); szName = NULL; break; } } if ( !szName ) continue; // Try to find the avatar award in the map for ( TitleAvatarAwardsDescription_t const *pAvAward = g_pMatchFramework->GetMatchTitle()->DescribeTitleAvatarAwards(); pAvAward && pAvAward->m_szAvatarAwardName; ++ pAvAward ) { if ( !Q_stricmp( szName, pAvAward->m_szAvatarAwardName ) ) { kvValue->SetInt( "", ( m_arrAvatarAwardsEarned.Find( pAvAward->m_idAvatarAward ) != m_arrAvatarAwardsEarned.InvalidIndex() ) ? 1 : 0 ); szName = NULL; break; } } if ( !szName ) continue; DevWarning( "pPlayerLocal(%s)->GetAwardsData(%s) UNKNOWN NAME!\n", GetName(), szName ); } } void PlayerLocal::UpdateAwardsData( KeyValues *pAwardsData ) { if ( !pAwardsData ) return; #ifdef _X360 if ( !m_xuid ) return; if ( GetAssumedSigninState() == eXUserSigninState_NotSignedIn ) return; #endif // Check our "guest" status #ifdef _GAMECONSOLE bool bRegisteredPlayer = false; for ( int k = 0; k < XBX_GetNumGameUsers(); ++ k ) { if ( XBX_GetUserId( k ) == m_iController ) { if ( XBX_GetUserIsGuest( k ) ) { DevMsg( "pPlayerLocal(%s)->UpdateAwardsData is unavailable for guests.\n", GetName() ); return; } bRegisteredPlayer = true; break; } } if ( !bRegisteredPlayer ) { DevMsg( "pPlayerLocal(%s)->UpdateAwardsData is unavailable for not participating gamers.\n", GetName() ); Assert( 0 ); // title code shouldn't be calling UpdateAwardsData for players not in active gameplay, title bug? return; } #endif for ( KeyValues *kvValue = pAwardsData->GetFirstValue(); kvValue; kvValue = kvValue->GetNextValue() ) { char const *szName = kvValue->GetName(); if ( !kvValue->GetInt( "" ) ) continue; // Check if the title allows the requested award KeyValues *kvTitleRequest = new KeyValues( "award" ); KeyValues::AutoDelete autodelete_kvTitleRequest( kvTitleRequest ); kvTitleRequest->SetString( "name", szName ); if ( !g_pMMF->GetMatchTitleGameSettingsMgr()->AllowClientProfileUpdate( kvTitleRequest ) ) continue; // Try to find the achievement in the achievement map for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements(); pAchievement && pAchievement->m_szAchievementName; ++ pAchievement ) { if ( !Q_stricmp( szName, pAchievement->m_szAchievementName ) ) { // Found the achievement to award if ( m_arrAchievementsEarned.Find( pAchievement->m_idAchievement ) == m_arrAchievementsEarned.InvalidIndex() ) { #ifdef _X360 XPendingAsyncAward_t *pAsync = new XPendingAsyncAward_t; Q_memset( pAsync, 0, sizeof( XPendingAsyncAward_t ) ); pAsync->m_flStartTimestamp = Plat_FloatTime(); pAsync->m_pLocalPlayer = this; pAsync->m_eType = XPendingAsyncAward_t::TYPE_ACHIEVEMENT; pAsync->m_pAchievementDesc = pAchievement; pAsync->m_xAchievement.dwUserIndex = m_iController; pAsync->m_xAchievement.dwAchievementId = pAchievement->m_idAchievement; DWORD dwErrCode = XUserWriteAchievements( 1, &pAsync->m_xAchievement, &pAsync->m_xOverlapped ); if ( dwErrCode == ERROR_IO_PENDING ) { DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) initiated async award.\n", GetName(), pAchievement->m_szAchievementName ); s_arrPendingAsyncAwards.AddToTail( pAsync ); m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement ); } else if ( ( dwErrCode == 1 ) || ( dwErrCode == 0 ) ) { delete pAsync; DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already awarded by system.\n", GetName(), pAchievement->m_szAchievementName ); m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement ); } else { delete pAsync; DevWarning( "pPlayerLocal(%s)->UpdateAwardsData(%s) failed to initiate async award.\n", GetName(), pAchievement->m_szAchievementName ); g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnProfileUnavailable", "iController", m_iController ) ); } #elif !defined( NO_STEAM ) bool bSteamResult = steamapicontext->SteamUserStats()->SetAchievement( pAchievement->m_szAchievementName ); if ( bSteamResult ) { m_bSaveTitleData[0] = true; // signal that stats must be stored m_arrAchievementsEarned.AddToTail( pAchievement->m_idAchievement ); DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) set achievement and stored stats.\n", GetName(), pAchievement->m_szAchievementName ); KeyValues *kvAwardedEvent = new KeyValues( "OnPlayerAward" ); kvAwardedEvent->SetInt( "iController", m_iController ); kvAwardedEvent->SetString( "award", pAchievement->m_szAchievementName ); g_pMatchEventsSubscription->BroadcastEvent( kvAwardedEvent ); } else { DevWarning( "pPlayerLocal(%s)->UpdateAwardsData(%s) failed to set in Steam.\n", GetName(), pAchievement->m_szAchievementName ); } #else DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) skipped.\n", GetName(), pAchievement->m_szAchievementName ); #endif } else { DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already earned.\n", GetName(), pAchievement->m_szAchievementName ); } szName = NULL; break; } } if ( !szName ) continue; // Try to find the avatar award in the map for ( TitleAvatarAwardsDescription_t const *pAvAward = g_pMatchFramework->GetMatchTitle()->DescribeTitleAvatarAwards(); pAvAward && pAvAward->m_szAvatarAwardName; ++ pAvAward ) { if ( !Q_stricmp( szName, pAvAward->m_szAvatarAwardName ) ) { // Found the avaward to award if ( m_arrAvatarAwardsEarned.Find( pAvAward->m_idAvatarAward ) == m_arrAvatarAwardsEarned.InvalidIndex() ) { #ifdef _X360 XPendingAsyncAward_t *pAsync = new XPendingAsyncAward_t; Q_memset( pAsync, 0, sizeof( XPendingAsyncAward_t ) ); pAsync->m_flStartTimestamp = Plat_FloatTime(); pAsync->m_pLocalPlayer = this; pAsync->m_eType = XPendingAsyncAward_t::TYPE_AVATAR_AWARD; pAsync->m_pAvatarAwardDesc = pAvAward; pAsync->m_xAvatarAsset.dwUserIndex = m_iController; pAsync->m_xAvatarAsset.dwAwardId = pAvAward->m_idAvatarAward; DWORD dwErrCode = XUserAwardAvatarAssets( 1, &pAsync->m_xAvatarAsset, &pAsync->m_xOverlapped ); if ( dwErrCode == ERROR_IO_PENDING ) { DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) initiated async award.\n", GetName(), pAvAward->m_szAvatarAwardName ); s_arrPendingAsyncAwards.AddToTail( pAsync ); m_arrAvatarAwardsEarned.AddToTail( pAvAward->m_idAvatarAward ); } else if ( ( dwErrCode == 1 ) || ( dwErrCode == 0 ) ) { delete pAsync; DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already awarded by system.\n", GetName(), pAvAward->m_szAvatarAwardName ); m_arrAvatarAwardsEarned.AddToTail( pAvAward->m_idAvatarAward ); if ( TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(), pAvAward->m_szTitleDataBitfieldStatName ) ) { TitleDataFieldsDescriptionSetBit( fdBitfield, this, true ); } } else { delete pAsync; DevWarning( "pPlayerLocal(%s)->UpdateAwardsData(%s) failed to initiate async award.\n", GetName(), pAvAward->m_szAvatarAwardName ); g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnProfileUnavailable", "iController", m_iController ) ); } #else DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) skipped.\n", GetName(), pAvAward->m_szAvatarAwardName ); #endif } else { DevMsg( "pPlayerLocal(%s)->UpdateAwardsData(%s) already earned.\n", GetName(), pAvAward->m_szAvatarAwardName ); } szName = NULL; break; } } if ( !szName ) continue; DevWarning( "pPlayerLocal(%s)->write_awards(%s) UNKNOWN NAME!\n", GetName(), szName ); } } #ifdef _X360 void PlayerLocal::UpdatePendingAwardsState() { bool bWarningFired = false; for ( int k = 0; k < s_arrPendingAsyncAwards.Count(); ++ k ) { XPendingAsyncAward_t *pPendingAsyncAward = s_arrPendingAsyncAwards[k]; if ( pPendingAsyncAward->m_pLocalPlayer && ( pPendingAsyncAward->m_pLocalPlayer != this ) ) continue; if ( !XHasOverlappedIoCompleted( &pPendingAsyncAward->m_xOverlapped ) ) continue; DWORD result = 0; DWORD dwXresult = XGetOverlappedResult( &pPendingAsyncAward->m_xOverlapped, &result, false ); bool bSuccessfullyEarned = ( dwXresult == ERROR_SUCCESS ); if ( this == pPendingAsyncAward->m_pLocalPlayer ) { if ( !bSuccessfullyEarned ) { switch ( pPendingAsyncAward->m_eType ) { case XPendingAsyncAward_t::TYPE_ACHIEVEMENT: m_arrAchievementsEarned.FindAndFastRemove( pPendingAsyncAward->m_pAchievementDesc->m_idAchievement ); break; case XPendingAsyncAward_t::TYPE_AVATAR_AWARD: m_arrAchievementsEarned.FindAndFastRemove( pPendingAsyncAward->m_pAvatarAwardDesc->m_idAvatarAward ); break; } if ( !bWarningFired ) { g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnProfileUnavailable", "iController", m_iController ) ); bWarningFired = true; } } else { if ( pPendingAsyncAward->m_eType == XPendingAsyncAward_t::TYPE_AVATAR_AWARD ) { if ( TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(), pPendingAsyncAward->m_pAvatarAwardDesc->m_szTitleDataBitfieldStatName ) ) { TitleDataFieldsDescriptionSetBit( fdBitfield, this, true ); } } KeyValues *kvAwardedEvent = new KeyValues( "OnPlayerAward" ); kvAwardedEvent->SetInt( "iController", m_iController ); if ( pPendingAsyncAward->m_eType == XPendingAsyncAward_t::TYPE_AVATAR_AWARD ) kvAwardedEvent->SetString( "award", pPendingAsyncAward->m_pAvatarAwardDesc->m_szAvatarAwardName ); if ( pPendingAsyncAward->m_eType == XPendingAsyncAward_t::TYPE_ACHIEVEMENT ) kvAwardedEvent->SetString( "award", pPendingAsyncAward->m_pAchievementDesc->m_szAchievementName ); g_pMatchEventsSubscription->BroadcastEvent( kvAwardedEvent ); } } // Remove the pending structure s_arrPendingAsyncAwards.Remove( k -- ); delete pPendingAsyncAward; } } #endif void PlayerLocal::EvaluateAwardsStateBasedOnStats() { TitleDataFieldsDescription_t const *pTitleDataStorage = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(); KeyValues *kvAwards = NULL; KeyValues::AutoDelete autodelete_kvAwards( kvAwards ); // // Evaluate the state of component achievements // for ( TitleAchievementsDescription_t const *pAchievement = g_pMatchFramework->GetMatchTitle()->DescribeTitleAchievements(); pAchievement && pAchievement->m_szAchievementName; ++ pAchievement ) { if ( pAchievement->m_numComponents <= 1 ) continue; if ( m_arrAchievementsEarned.Find( pAchievement->m_idAchievement ) != m_arrAchievementsEarned.InvalidIndex() ) continue; TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( pTitleDataStorage, CFmtStr( "%s[1]", pAchievement->m_szAchievementName ) ); int numComponentsSet = 0; for ( ; numComponentsSet < pAchievement->m_numComponents; ++ numComponentsSet, ++ fdBitfield ) { if ( !fdBitfield || !fdBitfield->m_szFieldName || (fdBitfield->m_eDataType != fdBitfield->DT_BITFIELD) ) { DevWarning( "EvaluateAwardsStateBasedOnStats for achievement [%s] error: invalid component configuration (comp#%d)!\n", pAchievement->m_szAchievementName, numComponentsSet + 1 ); break; } #ifdef _DEBUG // In debug make sure bitfields names match if ( Q_stricmp( fdBitfield->m_szFieldName, CFmtStr( "%s[%d]", pAchievement->m_szAchievementName, numComponentsSet + 1 ) ) ) { Assert( 0 ); } #endif if ( !TitleDataFieldsDescriptionGetBit( fdBitfield, this ) ) break; } if ( numComponentsSet == pAchievement->m_numComponents ) { // Achievement should be earned based on components if ( !kvAwards ) { kvAwards = new KeyValues( "write_award" ); autodelete_kvAwards.Assign( kvAwards ); } DevMsg( "PlayerLocal(%s)::EvaluateAwardsStateBasedOnStats is awarding %s\n", GetName(), pAchievement->m_szAchievementName ); kvAwards->SetInt( pAchievement->m_szAchievementName, 1 ); } } // // Evaluate the state of all avatar awards // for ( TitleAvatarAwardsDescription_t const *pAvatarAward = g_pMatchFramework->GetMatchTitle()->DescribeTitleAvatarAwards(); pAvatarAward && pAvatarAward->m_szAvatarAwardName; ++ pAvatarAward ) { if ( TitleDataFieldsDescription_t const *fdBitfield = TitleDataFieldsDescriptionFindByString( pTitleDataStorage, pAvatarAward->m_szTitleDataBitfieldStatName ) ) { if ( TitleDataFieldsDescriptionGetBit( fdBitfield, this ) ) { m_arrAvatarAwardsEarned.FindAndFastRemove( pAvatarAward->m_idAvatarAward ); m_arrAvatarAwardsEarned.AddToTail( pAvatarAward->m_idAvatarAward ); } } } // // Award all accumulated awards // UpdateAwardsData( kvAwards ); } void PlayerLocal::LoadGuestsTitleData() { #ifdef _GAMECONSOLE for ( int k = 0; k < XBX_GetNumGameUsers(); ++ k ) { int iCtrlr = XBX_GetUserId( k ); if ( iCtrlr == m_iController ) continue; if ( !XBX_GetUserIsGuest( k ) ) continue; DevMsg( "User%d stats inheriting from user%d.\n", iCtrlr, m_iController ); PlayerLocal *pGuest = ( PlayerLocal * ) g_pPlayerManager->GetLocalPlayer( iCtrlr ); Q_memcpy( pGuest->m_bufTitleData, m_bufTitleData, sizeof( m_bufTitleData ) ); } #endif } void PlayerLocal::OnProfileTitleDataLoaded( int iErrorCode ) { if ( iErrorCode ) { g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataLoadFailed", "iController", m_iController, "error", iErrorCode ) ); } else { LoadGuestsTitleData(); g_pMatchEventsSubscription->BroadcastEvent( new KeyValues( "OnProfileDataLoaded", "iController", m_iController ) ); } // Invite awaiting our title data if ( m_uiPlayerFlags & PLAYER_INVITE_AWAITING_TITLEDATA ) { m_uiPlayerFlags &=~PLAYER_INVITE_AWAITING_TITLEDATA; g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnInvite", "action", "join" ) ); } } XUSER_SIGNIN_STATE PlayerLocal::GetAssumedSigninState() { #ifdef _X360 return ( GetOnlineState() != STATE_OFFLINE ) ? ( eXUserSigninState_SignedInToLive ) : ( eXUserSigninState_SignedInLocally ); #elif !defined( NO_STEAM ) if ( steamapicontext->SteamUser() && steamapicontext->SteamUser()->BLoggedOn() ) return eXUserSigninState_SignedInToLive; else return eXUserSigninState_SignedInLocally; #else // No steam. #if defined( _PS3 ) return eXUserSigninState_SignedInLocally; #else return eXUserSigninState_NotSignedIn; #endif #endif } void PlayerLocal::SetNeedsSave() { for ( int ii=0; iiGetLocalPlayer( iCtrlr ); if ( !pPlayerLocal ) continue; Msg( " Name = %s\n", pPlayerLocal->GetName() ); TitleDataFieldsDescription_t const *fields = g_pMatchFramework->GetMatchTitle()->DescribeTitleDataStorage(); for ( ; fields && fields->m_szFieldName; ++ fields ) { switch ( fields->m_eDataType ) { case TitleDataFieldsDescription_t::DT_BITFIELD: Msg( "BITFIELD %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetBit( fields, pPlayerLocal ) ? 1 : 0 ); break; case TitleDataFieldsDescription_t::DT_uint8: Msg( "UINT8 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_uint16: Msg( "UINT16 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_uint32: Msg( "UINT32 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_float: Msg( "FLOAT %s = %.3f\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_uint64: Msg( "UINT64 %s = 0x%llX\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue( fields, pPlayerLocal ) ); break; } } } Msg( " ms_player_dump_properties finished.\n" ); } #ifdef _DEBUG CON_COMMAND_F( ms_player_award, "Awards the current player an award", FCVAR_CHEAT ) { int iCtrlr = args.FindArgInt( "ctrlr", XBX_GetPrimaryUserId() ); IPlayerLocal *pPlayer = g_pPlayerManager->GetLocalPlayer( iCtrlr ); if ( !pPlayer ) { DevWarning( "ERROR: Controller %d is not registered!\n", iCtrlr ); return; } KeyValues *kvAwards = new KeyValues( "write_awards", args.FindArg( "award" ), 1 ); KeyValues::AutoDelete autodelete( kvAwards ); (( PlayerLocal * )pPlayer)->UpdateAwardsData( kvAwards ); } #endif #if !defined(NO_STEAM) && !defined(_CERT) CON_COMMAND_F( ms_player_unaward, "UnAwards the current player an award", FCVAR_DEVELOPMENTONLY ) { if ( !CommandLine()->FindParm( "+ms_player_unaward" ) ) { Warning( "Error: You must pass +ms_player_unaward from command line!\n" ); return; } if ( !args.FindArg( "unaward" ) ) { Warning( "Syntax: +ms_player_unaward unaward ACHIEVEMENT|everything\n" ); return; } if ( !V_stricmp( "everything", args.FindArg( "unaward" ) ) ) { steamapicontext->SteamUserStats()->ResetAllStats( false ); steamapicontext->SteamUserStats()->ResetAllStats( true ); while ( steamapicontext->SteamRemoteStorage()->GetFileCount() > 0 ) { int nUnused; steamapicontext->SteamRemoteStorage()->FileDelete( steamapicontext->SteamRemoteStorage()->GetFileNameAndSize( 0, &nUnused ) ); } steamapicontext->SteamUserStats()->StoreStats(); Msg( "Everything was reset!\n" ); if ( !IsGameConsole() ) { g_pMatchExtensions->GetIVEngineClient()->ExecuteClientCmd( "exec config_default.cfg; exec joy_preset_1.cfg; host_writeconfig;" ); } return; } if ( steamapicontext->SteamUserStats()->ClearAchievement( args.FindArg( "unaward" ) ) ) { steamapicontext->SteamUserStats()->StoreStats(); Msg( "%s unawarded!\n", args.FindArg( "unaward" ) ); } else { Warning( "%s failed\n", args.FindArg( "unaward" ) ); } } #endif