|
|
//========= 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; i<TITLE_DATA_COUNT; ++i ) { m_bIsTitleDataBlockValid[ i ] = false; } m_bIsFreshPlayerProfile = false; if ( !XBX_GetPrimaryUserIsGuest() ) { XUserGetXUID( iController, &m_xuid ); }
DetectOnlineState(); #elif !defined( NO_STEAM )
CSteamID steamIDPlayer; if ( steamapicontext->SteamUser() ) { 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<TITLE_DATA_COUNT; ++i ) { m_bIsTitleDataBlockValid[ i ] = false; }
float flTimeStart; flTimeStart = Plat_FloatTime(); Msg( "Player %d : LoadTitleData...\n", m_iController );
//
// Enumerate the state of all achievements
//
{ DWORD numAchievements = 0; HANDLE hEnumerator = NULL; DWORD dwBytes; DWORD ret = XUserCreateAchievementEnumerator( 0, m_iController, INVALID_XUID, XACHIEVEMENT_DETAILS_TFC, 0, 80, &dwBytes, &hEnumerator ); if ( ret == ERROR_SUCCESS ) { CUtlVector< char > 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<int32>( 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<float>( 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<int32>( 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<MMDATA_TYPE_COUNT; ++i ) { m_MatchmakingData.mContribution[i][mmDataScope] = score_default.GetInt(); m_MatchmakingData.mMVPs[i][mmDataScope] = 0; m_MatchmakingData.mKills[i][mmDataScope] = 0; m_MatchmakingData.mDeaths[i][mmDataScope] = 0; m_MatchmakingData.mHeadShots[i][mmDataScope] = 0; m_MatchmakingData.mDamage[i][mmDataScope] = 0; m_MatchmakingData.mShotsFired[i][mmDataScope] = 0; m_MatchmakingData.mShotsHit[i][mmDataScope] = 0; m_MatchmakingData.mDominations[i][mmDataScope] = 0; m_MatchmakingData.mRoundsPlayed[i][mmDataScope] = 0; } #endif // #if defined ( _X360 )
}
const void * PlayerLocal::GetPlayerTitleData( int iTitleDataIndex ) { if ( iTitleDataIndex >= 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; ii<TITLE_DATA_COUNT; ++ii ) { m_bSaveTitleData[ii] = true; } }
CON_COMMAND_F( ms_player_dump_properties, "Prints a dump the current players property data", FCVAR_CHEAT ) { Msg( "[DMM] ms_player_dump_properties...\n" ); Msg( " Num game users: %d\n", XBX_GetNumGameUsers() ); for ( unsigned int iUserSlot = 0; iUserSlot < XBX_GetNumGameUsers(); ++ iUserSlot ) { int iCtrlr = iUserSlot; bool bGuest = false; #ifdef _GAMECONSOLE
iCtrlr = XBX_GetUserId( iUserSlot ); bGuest = !!XBX_GetUserIsGuest( iUserSlot ); #endif
Msg( "Slot%d ctrlr%d: %s\n", iUserSlot, iCtrlr, bGuest ? "guest" : "profile" ); IPlayerLocal *pPlayerLocal = g_pPlayerManager->GetLocalPlayer( 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<uint8>( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_uint16: Msg( "UINT16 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint16>( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_uint32: Msg( "UINT32 %s = %u\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint32>( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_float: Msg( "FLOAT %s = %.3f\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<float>( fields, pPlayerLocal ) ); break; case TitleDataFieldsDescription_t::DT_uint64: Msg( "UINT64 %s = 0x%llX\n", fields->m_szFieldName, TitleDataFieldsDescriptionGetValue<uint64>( 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
|