diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/achievementmgr.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/achievementmgr.cpp')
| -rw-r--r-- | game/shared/achievementmgr.cpp | 2074 |
1 files changed, 2074 insertions, 0 deletions
diff --git a/game/shared/achievementmgr.cpp b/game/shared/achievementmgr.cpp new file mode 100644 index 0000000..ba1fab2 --- /dev/null +++ b/game/shared/achievementmgr.cpp @@ -0,0 +1,2074 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#ifdef _WIN32 +#include "winerror.h" +#endif +#include "achievementmgr.h" +#include "icommandline.h" +#include "KeyValues.h" +#include "filesystem.h" +#include "inputsystem/InputEnums.h" +#include "usermessages.h" +#include "fmtstr.h" +#include "tier1/utlbuffer.h" +#ifdef CLIENT_DLL +#include "achievement_notification_panel.h" +#include "c_playerresource.h" +#include "gamestats.h" +#ifdef TF_CLIENT_DLL +#include "econ_item_inventory.h" +#endif //TF_CLIENT_DLL +#else +#include "enginecallback.h" +#endif // CLIENT_DLL +#ifndef _X360 +#include "steam/isteamuserstats.h" +#include "steam/isteamfriends.h" +#include "steam/isteamutils.h" +#include "steam/steam_api.h" +#include "steam/isteamremotestorage.h" +#else +#include "xbox/xbox_win32stubs.h" +#endif +#include "tier3/tier3.h" +#include "vgui/ILocalize.h" +#ifdef _X360 +#include "ixboxsystem.h" +#endif // _X360 +#include "engine/imatchmaking.h" +#include "tier0/vprof.h" + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) +#include "tf_gamerules.h" +#endif + +ConVar cc_achievement_debug( "achievement_debug", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Turn on achievement debug msgs." ); + +#ifdef CSTRIKE_DLL +//============================================================================= +// HPE_BEGIN: +// [Forrest] Allow achievements/stats to be turned off for a server +//============================================================================= +ConVar sv_nostats( "sv_nostats", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Disable collecting statistics and awarding achievements." ); +//============================================================================= +// HPE_END +//============================================================================= +#endif // CSTRIKE_DLL + +const char *COM_GetModDirectory(); + +extern ConVar developer; + +#define DEBUG_ACHIEVEMENTS_IN_RELEASE 0 + +#ifdef SWDS +// Hack this for now until we get steam_api recompiling in the Steam codebase. +ISteamUserStats *SteamUserStats() +{ + return NULL; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Write helper +//----------------------------------------------------------------------------- +//============================================================================= +// HPE_BEGIN +// [dwenger] Steam Cloud Support +//============================================================================= + +static void WriteAchievementGlobalState( KeyValues *pKV, bool bPersistToSteamCloud = false ) + +//============================================================================= +// HPE_END +//============================================================================= + +{ +#ifdef _X360 + if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED ) + return; +#endif + + char szFilename[_MAX_PATH]; + + if ( IsX360() ) + { + Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/%s_GameState.txt", COM_GetModDirectory() ); + } + else + { + Q_snprintf( szFilename, sizeof( szFilename ), "GameState.txt" ); + } + + // Never call pKV->SaveToFile!!!! + // Save to a buffer instead. + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + pKV->RecursiveSaveToFile( buf, 0 ); + filesystem->WriteFile( szFilename, NULL, buf ); + pKV->deleteThis(); + + //============================================================================= + // HPE_BEGIN + // [dwenger] Steam Cloud Support + //============================================================================= + + if ( bPersistToSteamCloud ) + { +#ifndef NO_STEAM + if ( IsX360() ) + { + Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/%s_GameState.txt", COM_GetModDirectory() ); + } + else + { + Q_snprintf( szFilename, sizeof( szFilename ), "GameState.txt" ); + } + + ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface( + SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL; + + if (pRemoteStorage) + { + int32 availableBytes = 0; + int32 totalBytes = 0; + if ( pRemoteStorage->GetQuota( &totalBytes, &availableBytes ) ) + { + if ( totalBytes > 0 ) + { + int32 filesize = (int32)filesystem->Size(szFilename); + + if (filesize > 0) + { + char* pData = new char[filesize]; + + if (pData) + { + // Read in the data from the file system GameState.txt file + FileHandle_t handle = filesystem->Open(szFilename, "r"); + + if (handle) + { + int32 nRead = filesystem->Read(pData, filesize, handle); + + filesystem->Close(handle); + + if (nRead == filesize) + { + // Write out the data to steam cloud + pRemoteStorage->FileWrite(szFilename, pData, filesize); + } + } + + // Delete the data array + delete []pData; + } + } + } + } + } +#endif + } + + //============================================================================= + // HPE_END + //============================================================================= + +#ifdef _X360 + if ( xboxsystem ) + { + xboxsystem->FinishContainerWrites(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Async save thread +//----------------------------------------------------------------------------- +class CAchievementSaveThread : public CWorkerThread +{ +public: + CAchievementSaveThread() : + m_pKV( NULL ) + { + SetName( "AchievementSaveThread" ); + } + + ~CAchievementSaveThread() + { + } + + enum + { + CALL_FUNC, + EXIT, + }; + + void WriteAchievementGlobalState( KeyValues *pKV ) + { + Assert( !m_pKV ); + m_pKV = pKV; + CallWorker( CALL_FUNC ); + Assert( !m_pKV ); + } + + int Run() + { + unsigned nCall; + while ( WaitForCall( &nCall ) ) + { + if ( nCall == EXIT ) + { + Reply( 1 ); + break; + } + + KeyValues *pKV = m_pKV; + m_pKV = NULL; + Reply( 1 ); + ::WriteAchievementGlobalState( pKV ); + } + return 0; + } + +private: + KeyValues *m_pKV; +}; + +static CAchievementSaveThread g_AchievementSaveThread; + + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +//============================================================================= +// HPE_BEGIN +// [dwenger] Steam Cloud Support +//============================================================================= + +CAchievementMgr::CAchievementMgr( SteamCloudPersisting ePersistToSteamCloud ) : CAutoGameSystemPerFrame( "CAchievementMgr" ) + +//============================================================================= +// HPE_END +//============================================================================= + +#if !defined(NO_STEAM) +, m_CallbackUserStatsReceived( this, &CAchievementMgr::Steam_OnUserStatsReceived ), +m_CallbackUserStatsStored( this, &CAchievementMgr::Steam_OnUserStatsStored ) +#endif +{ + SetDefLessFunc( m_mapAchievement ); + SetDefLessFunc( m_mapMetaAchievement ); + m_flLastClassChangeTime = 0; + m_flTeamplayStartTime = 0; + m_iMiniroundsCompleted = 0; + m_szMap[0] = 0; + m_bSteamDataDirty = false; + m_bGlobalStateDirty = false; + m_bGlobalStateLoaded = false; + m_bCheatsEverOn = false; + m_flTimeLastSaved = 0; + + //============================================================================= + // HPE_BEGIN + // [dwenger] Steam Cloud Support + //============================================================================= + + if ( ePersistToSteamCloud == SteamCloudPersist_Off ) + { + m_bPersistToSteamCloud = false; + } + else + { + m_bPersistToSteamCloud = true; + } + + //============================================================================= + // HPE_END + //============================================================================= + + m_AchievementsAwarded.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initializer +//----------------------------------------------------------------------------- +bool CAchievementMgr::Init() +{ + // We can be created on either client (for multiplayer games) or server + // (for single player), so register ourselves with the engine so UI has a uniform place + // to go get the pointer to us + +#ifdef _DEBUG + // There can be only one achievement manager instance; no one else should be registered + IAchievementMgr *pAchievementMgr = engine->GetAchievementMgr(); + Assert( NULL == pAchievementMgr ); +#endif // _DEBUG + + // register ourselves + engine->SetAchievementMgr( this ); + + // register for events +#ifdef GAME_DLL + ListenForGameEvent( "entity_killed" ); + ListenForGameEvent( "game_init" ); +#else + ListenForGameEvent( "player_death" ); + ListenForGameEvent( "player_stats_updated" ); + usermessages->HookMessage( "AchievementEvent", MsgFunc_AchievementEvent ); +#endif // CLIENT_DLL + +#ifdef TF_CLIENT_DLL + ListenForGameEvent( "localplayer_changeclass" ); + ListenForGameEvent( "localplayer_changeteam" ); + ListenForGameEvent( "teamplay_round_start" ); + ListenForGameEvent( "teamplay_round_win" ); +#endif // TF_CLIENT_DLL + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: called at init time after all systems are init'd. We have to +// do this in PostInit because the Steam app ID is not available earlier +//----------------------------------------------------------------------------- +void CAchievementMgr::PostInit() +{ + if ( !g_AchievementSaveThread.IsAlive() ) + { + g_AchievementSaveThread.Start(); +#ifdef WIN32 + if ( IsX360() ) + { + ThreadSetAffinity( (ThreadHandle_t)g_AchievementSaveThread.GetThreadHandle(), XBOX_PROCESSOR_3 ); + } +#endif // WIN32 + } + + // get current game dir + const char *pGameDir = COM_GetModDirectory(); + + CBaseAchievementHelper *pAchievementHelper = CBaseAchievementHelper::s_pFirst; + while ( pAchievementHelper ) + { + // create and initialize all achievements and insert them in our map + CBaseAchievement *pAchievement = pAchievementHelper->m_pfnCreate(); + pAchievement->m_pAchievementMgr = this; + pAchievement->Init(); + pAchievement->CalcProgressMsgIncrement(); + + // only add an achievement if it does not have a game filter (only compiled into the game it + // applies to, or truly cross-game) or, if it does have a game filter, the filter matches current game. + // (e.g. EP 1/2/... achievements are in shared binary but are game specific, they have a game filter for runtime check.) + const char *pGameDirFilter = pAchievement->m_pGameDirFilter; + if ( !pGameDirFilter || ( 0 == Q_strcmp( pGameDir, pGameDirFilter ) ) ) + { + m_mapAchievement.Insert( pAchievement->GetAchievementID(), pAchievement ); + if ( pAchievement->IsMetaAchievement() ) + { + m_mapMetaAchievement.Insert( pAchievement->GetAchievementID(), dynamic_cast<CAchievement_AchievedCount*>(pAchievement) ); + } + } + else + { + // achievement is not for this game, don't use it + delete pAchievement; + } + + pAchievementHelper = pAchievementHelper->m_pNext; + } + + FOR_EACH_MAP( m_mapAchievement, iter ) + { + m_vecAchievement.AddToTail( m_mapAchievement[iter] ); + } + + // load global state from file + LoadGlobalState(); + + // download achievements/stats from Steam/XBox Live + DownloadUserData(); + +} + +//----------------------------------------------------------------------------- +// Purpose: Shuts down the achievement manager +//----------------------------------------------------------------------------- +void CAchievementMgr::Shutdown() +{ + g_AchievementSaveThread.CallWorker( CAchievementSaveThread::EXIT ); + + SaveGlobalState( false ); // we just told the thread to shutdown so don't try an async save here + + FOR_EACH_MAP( m_mapAchievement, iter ) + { + delete m_mapAchievement[iter]; + } + m_mapAchievement.RemoveAll(); + m_mapMetaAchievement.RemoveAll(); + m_vecAchievement.RemoveAll(); + m_vecKillEventListeners.RemoveAll(); + m_vecMapEventListeners.RemoveAll(); + m_vecComponentListeners.RemoveAll(); + m_AchievementsAwarded.RemoveAll(); + m_bGlobalStateLoaded = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Cleans up all achievements and then re-initializes them +//----------------------------------------------------------------------------- +void CAchievementMgr::InitializeAchievements() +{ + Shutdown(); + PostInit(); +} + +#ifdef CLIENT_DLL +extern const ConVar *sv_cheats; +#endif + +#ifdef GAME_DLL +void CAchievementMgr::FrameUpdatePostEntityThink() +{ + Update( 0.0f ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Do per-frame handling +//----------------------------------------------------------------------------- +void CAchievementMgr::Update( float frametime ) +{ +#ifdef CLIENT_DLL + if ( !sv_cheats ) + { + sv_cheats = cvar->FindVar( "sv_cheats" ); + } +#endif + +#ifndef _DEBUG + // keep track if cheats have ever been turned on during this level + if ( !WereCheatsEverOn() ) + { + if ( sv_cheats && sv_cheats->GetBool() ) + { + m_bCheatsEverOn = true; + } + } +#endif + + // Call think functions. Work backwards, because we may remove achievements from the list. + int iCount = m_vecThinkListeners.Count(); + for ( int i = iCount-1; i >= 0; i-- ) + { + if ( m_vecThinkListeners[i].m_flThinkTime < gpGlobals->curtime ) + { + m_vecThinkListeners[i].pAchievement->Think(); + + // The think function may have pushed out the think time. If not, remove ourselves from the list. + if ( m_vecThinkListeners[i].pAchievement->IsAchieved() || m_vecThinkListeners[i].m_flThinkTime < gpGlobals->curtime ) + { + m_vecThinkListeners.Remove(i); + } + } + } + + if ( m_bSteamDataDirty ) + { + UploadUserData(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called on level init +//----------------------------------------------------------------------------- +void CAchievementMgr::LevelInitPreEntity() +{ + m_bCheatsEverOn = false; + + // load global state if we haven't already; X360 users may not have had a storage device available or selected at boot time + EnsureGlobalStateLoaded(); + +#ifdef GAME_DLL + // For single-player games, achievement mgr must live on the server. (Only the server has detailed knowledge of game state.) + Assert( !GameRules()->IsMultiplayer() ); +#else + // For multiplayer games, achievement mgr must live on the client. (Only the client can read/write player state from Steam/XBox Live.) + Assert( GameRules()->IsMultiplayer() ); +#endif + + // clear list of achievements listening for events + m_vecKillEventListeners.RemoveAll(); + m_vecMapEventListeners.RemoveAll(); + m_vecComponentListeners.RemoveAll(); + + m_AchievementsAwarded.RemoveAll(); + + m_flLastClassChangeTime = 0; + m_flTeamplayStartTime = 0; + m_iMiniroundsCompleted = 0; + + // client and server have map names available in different forms (full path on client, just file base name on server), + // cache it in base file name form here so we don't have to have different code paths each time we access it +#ifdef CLIENT_DLL + Q_FileBase( engine->GetLevelName(), m_szMap, ARRAYSIZE( m_szMap ) ); +#else + Q_strncpy( m_szMap, gpGlobals->mapname.ToCStr(), ARRAYSIZE( m_szMap ) ); +#endif // CLIENT_DLL + + if ( IsX360() ) + { + // need to remove the .360 extension on the end of the map name + char *pExt = Q_stristr( m_szMap, ".360" ); + if ( pExt ) + { + *pExt = '\0'; + } + } + + // look through all achievements, see which ones we want to have listen for events + FOR_EACH_MAP( m_mapAchievement, iAchievement ) + { + CBaseAchievement *pAchievement = m_mapAchievement[iAchievement]; + + // if the achievement only applies to a specific map, and it's not the current map, skip it + const char *pMapNameFilter = pAchievement->m_pMapNameFilter; + if ( pMapNameFilter && ( 0 != Q_strcmp( m_szMap, pMapNameFilter ) ) ) + continue; + + // if the achievement needs kill events, add it as a listener + if ( pAchievement->GetFlags() & ACH_LISTEN_KILL_EVENTS ) + { + m_vecKillEventListeners.AddToTail( pAchievement ); + } + // if the achievement needs map events, add it as a listener + if ( pAchievement->GetFlags() & ACH_LISTEN_MAP_EVENTS ) + { + m_vecMapEventListeners.AddToTail( pAchievement ); + } + // if the achievement needs map events, add it as a listener + if ( pAchievement->GetFlags() & ACH_LISTEN_COMPONENT_EVENTS ) + { + m_vecComponentListeners.AddToTail( pAchievement ); + } + if ( pAchievement->IsActive() ) + { + pAchievement->ListenForEvents(); + } + } + + m_flLevelInitTime = gpGlobals->curtime; +} + + +//----------------------------------------------------------------------------- +// Purpose: called on level shutdown +//----------------------------------------------------------------------------- +void CAchievementMgr::LevelShutdownPreEntity() +{ + // make all achievements stop listening for events + FOR_EACH_MAP( m_mapAchievement, iAchievement ) + { + CBaseAchievement *pAchievement = m_mapAchievement[iAchievement]; + if ( !pAchievement->AlwaysListen() ) + { + pAchievement->StopListeningForAllEvents(); + } + } + + // save global state if we have any changes + SaveGlobalStateIfDirty(); + + UploadUserData(); +} + +//----------------------------------------------------------------------------- +// Purpose: returns achievement for specified ID +//----------------------------------------------------------------------------- +CBaseAchievement *CAchievementMgr::GetAchievementByID( int iAchievementID ) +{ + int iAchievement = m_mapAchievement.Find( iAchievementID ); + if ( iAchievement != m_mapAchievement.InvalidIndex() ) + { + return m_mapAchievement[iAchievement]; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: returns achievement with specified name. NOTE: this iterates through +// all achievements to find the name, intended for debugging purposes. +// Use GetAchievementByID for fast lookup. +//----------------------------------------------------------------------------- +CBaseAchievement *CAchievementMgr::GetAchievementByName( const char *pchName ) +{ + VPROF("GetAchievementByName"); + FOR_EACH_MAP_FAST( m_mapAchievement, i ) + { + CBaseAchievement *pAchievement = m_mapAchievement[i]; + if ( pAchievement && 0 == ( Q_stricmp( pchName, pAchievement->GetName() ) ) ) + return pAchievement; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the achievement with the specified name has been achieved +//----------------------------------------------------------------------------- +bool CAchievementMgr::HasAchieved( const char *pchName ) +{ + CBaseAchievement *pAchievement = GetAchievementByName( pchName ); + if ( pAchievement ) + return pAchievement->IsAchieved(); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: downloads user data from Steam or XBox Live +//----------------------------------------------------------------------------- +void CAchievementMgr::DownloadUserData() +{ + if ( IsPC() ) + { +#ifndef NO_STEAM + if ( steamapicontext->SteamUserStats() ) + { + // request stat download; will get called back at OnUserStatsReceived when complete + steamapicontext->SteamUserStats()->RequestCurrentStats(); + } +#endif + } + else if ( IsX360() ) + { +#if defined( _X360 ) + if ( XBX_GetPrimaryUserId() == INVALID_USER_ID ) + return; + + // Download achievements from XBox Live + bool bDownloadSuccessful = true; + int nTotalAchievements = 99; + uint bytes; + int ret = xboxsystem->EnumerateAchievements( XBX_GetPrimaryUserId(), 0, 0, nTotalAchievements, &bytes, 0, false ); + if ( ret != ERROR_SUCCESS ) + { + Warning( "Enumerate Achievements failed! Error %d", ret ); + bDownloadSuccessful = false; + } + + // Enumerate the achievements from Live + void *pBuffer = new byte[bytes]; + if ( bDownloadSuccessful ) + { + ret = xboxsystem->EnumerateAchievements( XBX_GetPrimaryUserId(), 0, 0, nTotalAchievements, pBuffer, bytes, false ); + + if ( ret != nTotalAchievements ) + { + Warning( "Enumerate Achievements failed! Error %d", ret ); + bDownloadSuccessful = false; + } + } + + if ( bDownloadSuccessful ) + { + // Give live a chance to mark achievements as unlocked, in case the achievement manager + // wasn't able to get that data (storage device missing, read failure, etc) + XACHIEVEMENT_DETAILS *pXboxAchievements = (XACHIEVEMENT_DETAILS*)pBuffer; + for ( int i = 0; i < nTotalAchievements; ++i ) + { + CBaseAchievement *pAchievement = GetAchievementByID( pXboxAchievements[i].dwId ); + if ( !pAchievement ) + continue; + + // Give Live a chance to claim the achievement as unlocked + if ( AchievementEarned( pXboxAchievements[i].dwFlags ) ) + { + pAchievement->SetAchieved( true ); + } + } + } + + delete pBuffer; +#endif // X360 + } +} + +const char *COM_GetModDirectory() +{ + static char modDir[MAX_PATH]; + if ( Q_strlen( modDir ) == 0 ) + { + const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); + Q_strncpy( modDir, gamedir, sizeof(modDir) ); + if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) ) + { + Q_StripLastDir( modDir, sizeof(modDir) ); + int dirlen = Q_strlen( modDir ); + Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen ); + } + } + + return modDir; +} + +//----------------------------------------------------------------------------- +// Purpose: uploads user data to steam +//----------------------------------------------------------------------------- +void CAchievementMgr::UploadUserData() +{ + if ( IsPC() ) + { +#ifndef NO_STEAM + if ( steamapicontext->SteamUserStats() ) + { + // Upload current Steam client achievements & stats state to Steam. Will get called back at OnUserStatsStored when complete. + // Only values previously set via SteamUserStats() get uploaded + steamapicontext->SteamUserStats()->StoreStats(); + m_bSteamDataDirty = false; + } +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: loads global state from file +//----------------------------------------------------------------------------- +void CAchievementMgr::LoadGlobalState() +{ + if ( IsX360() ) + { +#ifdef _X360 + if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED ) + return; +#endif + } + + char szFilename[_MAX_PATH]; + + if ( IsX360() ) + { + Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/%s_GameState.txt", COM_GetModDirectory() ); + } + else + { + Q_snprintf( szFilename, sizeof( szFilename ), "GameState.txt" ); + } + + //============================================================================= + // HPE_BEGIN + // [dwenger] Steam Cloud Support + //============================================================================= + + if ( m_bPersistToSteamCloud ) + { +#ifndef NO_STEAM + ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface( + SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL; + + if (pRemoteStorage) + { + if (pRemoteStorage->FileExists(szFilename)) + { + int32 fileSize = pRemoteStorage->GetFileSize(szFilename); + + if (fileSize > 0) + { + // Allocate space for the file data + char* pData = new char[fileSize]; + + if (pData) + { + int32 sizeRead = pRemoteStorage->FileRead(szFilename, pData, fileSize); + + if (sizeRead == fileSize) + { + // Write out data to a filesystem GameState file that can be read by the original code below + FileHandle_t handle = filesystem->Open(szFilename, "w"); + + if (handle) + { + filesystem->Write(pData, fileSize, handle); + + filesystem->Close(handle); + } + } + + // Delete the data array + delete []pData; + } + } + } + } +#endif + } + + //============================================================================= + // HPE_END + //============================================================================= + + KeyValues *pKV = new KeyValues("GameState" ); + if ( pKV->LoadFromFile( filesystem, szFilename, "MOD" ) ) + { + KeyValues *pNode = pKV->GetFirstSubKey(); + while ( pNode ) + { + // look up this achievement + int iAchievementID = pNode->GetInt( "id", 0 ); + if ( iAchievementID > 0 ) + { + CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID ); + if ( pAchievement ) + { + pAchievement->ApplySettings(pNode); + } + } + + pNode = pNode->GetNextKey(); + } + + m_bGlobalStateLoaded = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: saves global state to file +//----------------------------------------------------------------------------- + +void CAchievementMgr::SaveGlobalState( bool bAsync ) +{ + VPROF_BUDGET( "CAchievementMgr::SaveGlobalState", "Achievements" ); + + KeyValues *pKV = new KeyValues("GameState" ); + FOR_EACH_MAP( m_mapAchievement, i ) + { + CBaseAchievement *pAchievement = m_mapAchievement[i]; + if ( pAchievement->ShouldSaveGlobal() ) + { + KeyValues *pNode = pKV->CreateNewKey(); + pNode->SetInt( "id", pAchievement->GetAchievementID() ); + + pAchievement->GetSettings(pNode); + } + } + + if ( !bAsync ) + { + WriteAchievementGlobalState( pKV, m_bPersistToSteamCloud ); + } + else + { + g_AchievementSaveThread.WriteAchievementGlobalState( pKV ); + } + + m_flTimeLastSaved = Plat_FloatTime(); + m_bGlobalStateDirty = false; +} + +//----------------------------------------------------------------------------- +// Purpose: loads global state if we have not already successfully loaded it +//----------------------------------------------------------------------------- +void CAchievementMgr::EnsureGlobalStateLoaded() +{ + if ( !m_bGlobalStateLoaded ) + { + LoadGlobalState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: saves global state to file if there have been any changes +//----------------------------------------------------------------------------- +void CAchievementMgr::SaveGlobalStateIfDirty( bool bAsync ) +{ + if ( m_bGlobalStateDirty ) + { + SaveGlobalState( bAsync ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: awards specified achievement +//----------------------------------------------------------------------------- +void CAchievementMgr::AwardAchievement( int iAchievementID ) +{ + CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID ); + Assert( pAchievement ); + if ( !pAchievement ) + return; + + if ( !pAchievement->AlwaysEnabled() && !CheckAchievementsEnabled() ) + { + Msg( "Achievements disabled, ignoring achievement unlock for %s\n", pAchievement->GetName() ); + return; + } + + if ( pAchievement->IsAchieved() ) + { + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "Achievement award called but already achieved: %s\n", pAchievement->GetName() ); + } + return; + } + pAchievement->SetAchieved( true ); + +#ifdef CLIENT_DLL + if ( gamestats ) + { + gamestats->Event_AchievementProgress( pAchievement->GetAchievementID(), pAchievement->GetName() ); + } +#endif + + //============================================================================= + // HPE_BEGIN + //============================================================================= + + // [dwenger] Necessary for sorting achievements by award time + pAchievement->OnAchieved(); + + // [tj] + IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned_local" ); + if ( event ) + { + event->SetInt( "achievement", pAchievement->GetAchievementID() ); + gameeventmanager->FireEventClientSide( event ); + } + + //============================================================================= + // HPE_END + //============================================================================= + + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "Achievement awarded: %s\n", pAchievement->GetName() ); + } + + // save state at next good opportunity. (Don't do it immediately, may hitch at bad time.) + SetDirty( true ); + + if ( IsPC() ) + { +#ifndef NO_STEAM + if ( steamapicontext->SteamUserStats() ) + { + VPROF_BUDGET( "AwardAchievement", VPROF_BUDGETGROUP_STEAM ); + // set this achieved in the Steam client + bool bRet = steamapicontext->SteamUserStats()->SetAchievement( pAchievement->GetName() ); + // Assert( bRet ); + if ( bRet ) + { + m_AchievementsAwarded.AddToTail( iAchievementID ); + } + } +#endif + } + else if ( IsX360() ) + { +#ifdef _X360 + if ( xboxsystem ) + xboxsystem->AwardAchievement( XBX_GetPrimaryUserId(), iAchievementID ); +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: updates specified achievement +//----------------------------------------------------------------------------- +void CAchievementMgr::UpdateAchievement( int iAchievementID, int nData ) +{ + CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID ); + Assert( pAchievement ); + if ( !pAchievement ) + return; + + if ( !pAchievement->AlwaysEnabled() && !CheckAchievementsEnabled() ) + { + Msg( "Achievements disabled, ignoring achievement update for %s\n", pAchievement->GetName() ); + return; + } + + if ( pAchievement->IsAchieved() ) + { + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "Achievement update called but already achieved: %s\n", pAchievement->GetName() ); + } + return; + } + + pAchievement->UpdateAchievement( nData ); +} + +//----------------------------------------------------------------------------- +// Purpose: clears state for all achievements +//----------------------------------------------------------------------------- +void CAchievementMgr::PreRestoreSavedGame() +{ + // load global state if we haven't already; X360 users may not have had a storage device available or selected at boot time + EnsureGlobalStateLoaded(); + + FOR_EACH_MAP( m_mapAchievement, i ) + { + m_mapAchievement[i]->PreRestoreSavedGame(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: clears state for all achievements +//----------------------------------------------------------------------------- +void CAchievementMgr::PostRestoreSavedGame() +{ + FOR_EACH_MAP( m_mapAchievement, i ) + { + m_mapAchievement[i]->PostRestoreSavedGame(); + } +} + +extern bool IsInCommentaryMode( void ); +//----------------------------------------------------------------------------- +// Purpose: checks if achievements are enabled +//----------------------------------------------------------------------------- +bool CAchievementMgr::CheckAchievementsEnabled() +{ + // if PC, Steam must be running and user logged in + if ( IsPC() && !LoggedIntoSteam() ) + { + Msg( "Achievements disabled: Steam not running.\n" ); + return false; + } + +#if defined( _X360 ) + uint state = XUserGetSigninState( XBX_GetPrimaryUserId() ); + if ( state == eXUserSigninState_NotSignedIn ) + { + Msg( "Achievements disabled: not signed in to XBox user account.\n" ); + return false; + } +#endif + + // can't be in commentary mode, user is invincible + if ( IsInCommentaryMode() ) + { + Msg( "Achievements disabled: in commentary mode.\n" ); + return false; + } + +#ifdef CLIENT_DLL + // achievements disabled if playing demo + if ( engine->IsPlayingDemo() ) + { + Msg( "Achievements disabled: demo playing.\n" ); + return false; + } +#endif // CLIENT_DLL + +#ifdef CSTRIKE_DLL + //============================================================================= + // HPE_BEGIN: + // [Forrest] Allow achievements/stats to be turned off for a server + //============================================================================= + if ( sv_nostats.GetBool() ) + { + // prevent message spam + const float fNotificationCooldown = 60.0f; + static float fNextNotification = 0.0f; + if (gpGlobals->curtime >= fNextNotification) + { + Msg( "Achievements and stats disabled: sv_nostats is set.\n" ); + fNextNotification = gpGlobals->curtime + fNotificationCooldown; + } + + return false; + } + //============================================================================= + // HPE_END + //============================================================================= +#endif // CSTRIKE_DLL + +#if defined(TF_DLL) || defined(TF_CLIENT_DLL) + // no achievements for now in training + if ( TFGameRules() && TFGameRules()->IsInTraining() && TFGameRules()->AllowTrainingAchievements() == false ) + { + return false; + } + + ConVarRef tf_bot_offline_practice( "tf_bot_offline_practice" ); + // no achievements for offline practice + if ( tf_bot_offline_practice.GetInt() != 0 ) + { + return false; + } +#endif + +#if DEBUG_ACHIEVEMENTS_IN_RELEASE + return true; +#endif + + if ( IsPC() ) + { + // Don't award achievements if cheats are turned on. + if ( WereCheatsEverOn() ) + { +#ifndef NO_STEAM + // Cheats get turned on automatically if you run with -dev which many people do internally, so allow cheats if developer is turned on and we're not running + // on Steam public + if ( developer.GetInt() == 0 || ( k_EUniverseInvalid == GetUniverse() ) || ( k_EUniversePublic == GetUniverse() ) ) + { + Msg( "Achievements disabled: cheats turned on in this app session.\n" ); + return false; + } +#endif + } + } + + return true; +} + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Returns the whether all of the local player's team mates are +// on her friends list, and if there are at least the specified # of +// teammates. Involves cross-process calls to Steam so this is mildly +// expensive, don't call this every frame. +//----------------------------------------------------------------------------- +bool CalcPlayersOnFriendsList( int iMinFriends ) +{ + // Got message during connection + if ( !g_PR ) + return false; + + Assert( g_pGameRules->IsMultiplayer() ); + + // Do a cheap rejection: check teammate count first to see if we even need to bother checking w/Steam + // Subtract 1 for the local player. + if ( CalcPlayerCount()-1 < iMinFriends ) + return false; + + // determine local player team + int iLocalPlayerIndex = GetLocalPlayerIndex(); + uint64 XPlayerUid = 0; + + if ( IsPC() ) + { +#ifndef NO_STEAM + if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() || !g_pGameRules->IsMultiplayer() ) +#endif + return false; + + } + else if ( IsX360() ) + { + if ( !matchmaking ) + return false; + + XPlayerUid = XBX_GetPrimaryUserId(); + } + else + { + // other platforms...? + return false; + } + // Loop through the players + int iTotalFriends = 0; + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + // find all players who are on the local player's team + if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) ) + { + if ( IsPC() ) + { + player_info_t pi; + if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) + continue; + if ( !pi.friendsID ) + continue; +#ifndef NO_STEAM + // check and see if they're on the local player's friends list + CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); + if ( !steamapicontext->SteamFriends()->HasFriend( steamID, /*k_EFriendFlagImmediate*/ 0x04 ) ) + continue; +#endif + } + else if ( IsX360() ) + { + uint64 XUid[1]; + XUid[0] = matchmaking->PlayerIdToXuid( iPlayerIndex ); + BOOL bFriend; +#ifdef _X360 + XUserAreUsersFriends( XPlayerUid, XUid, 1, &bFriend, NULL ); +#endif // _X360 + if ( !bFriend ) + continue; + } + + iTotalFriends++; + } + } + + return (iTotalFriends >= iMinFriends); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns whether there are a specified # of teammates who all belong +// to same clan as local player. Involves cross-process calls to Steam +// so this is mildly expensive, don't call this every frame. +//----------------------------------------------------------------------------- +bool CalcHasNumClanPlayers( int iClanTeammates ) +{ + Assert( g_pGameRules->IsMultiplayer() ); + + if ( IsPC() ) + { +#ifndef _X360 + // Do a cheap rejection: check teammate count first to see if we even need to bother checking w/Steam + // Subtract 1 for the local player. + if ( CalcPlayerCount()-1 < iClanTeammates ) + return false; + + if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUtils() || !g_pGameRules->IsMultiplayer() ) + return false; + + // determine local player team + int iLocalPlayerIndex = GetLocalPlayerIndex(); + + for ( int iClan = 0; iClan < steamapicontext->SteamFriends()->GetClanCount(); iClan++ ) + { + int iClanMembersOnTeam = 0; + CSteamID clanID = steamapicontext->SteamFriends()->GetClanByIndex( iClan ); + // enumerate all players + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) ) + { + player_info_t pi; + if ( engine->GetPlayerInfo( iPlayerIndex, &pi ) && ( pi.friendsID ) ) + { + // check and see if they're on the local player's friends list + CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); + if ( steamapicontext->SteamFriends()->IsUserInSource( steamID, clanID ) ) + { + iClanMembersOnTeam++; + if ( iClanMembersOnTeam == iClanTeammates ) + return true; + } + } + } + } + } +#endif + return false; + } + else if ( IsX360() ) + { + // TODO: implement for 360 + return false; + } + else + { + // other platforms...? + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the # of teammates of the local player +//----------------------------------------------------------------------------- +int CalcTeammateCount() +{ + Assert( g_pGameRules->IsMultiplayer() ); + + // determine local player team + int iLocalPlayerIndex = GetLocalPlayerIndex(); + int iLocalPlayerTeam = g_PR->GetTeam( iLocalPlayerIndex ); + + int iNumTeammates = 0; + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + // find all players who are on the local player's team + if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) && ( g_PR->GetTeam( iPlayerIndex ) == iLocalPlayerTeam ) ) + { + iNumTeammates++; + } + } + return iNumTeammates; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CalcPlayerCount() +{ + int iCount = 0; + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + // find all players who are on the local player's team + if( g_PR->IsConnected( iPlayerIndex ) ) + { + iCount++; + } + } + return iCount; +} + +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: Resets all achievements. For debugging purposes only +//----------------------------------------------------------------------------- +void CAchievementMgr::ResetAchievements() +{ + if ( !IsPC() ) + { + DevMsg( "Only available on PC\n" ); + return; + } + + if ( !LoggedIntoSteam() ) + { + Msg( "Steam not running, achievements disabled. Cannot reset achievements.\n" ); + return; + } + + FOR_EACH_MAP( m_mapAchievement, i ) + { + CBaseAchievement *pAchievement = m_mapAchievement[i]; + ResetAchievement_Internal( pAchievement ); + } + +#ifndef NO_STEAM + if ( steamapicontext->SteamUserStats() ) + { + steamapicontext->SteamUserStats()->StoreStats(); + } +#endif + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "All achievements reset.\n" ); + } +} + +void CAchievementMgr::ResetAchievement( int iAchievementID ) +{ + if ( !IsPC() ) + { + DevMsg( "Only available on PC\n" ); + return; + } + + if ( !LoggedIntoSteam() ) + { + Msg( "Steam not running, achievements disabled. Cannot reset achievements.\n" ); + return; + } + + CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID ); + Assert( pAchievement ); + if ( pAchievement ) + { + ResetAchievement_Internal( pAchievement ); +#ifndef NO_STEAM + if ( steamapicontext->SteamUserStats() ) + { + steamapicontext->SteamUserStats()->StoreStats(); + } +#endif + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "Achievement %s reset.\n", pAchievement->GetName() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resets all achievements. For debugging purposes only +//----------------------------------------------------------------------------- +void CAchievementMgr::PrintAchievementStatus() +{ + if ( IsPC() && !LoggedIntoSteam() ) + { + Msg( "Steam not running, achievements disabled. Cannot view or unlock achievements.\n" ); + return; + } + + Msg( "%42s %-20s %s\n", "Name:", "Status:", "Point value:" ); + int iTotalAchievements = 0, iTotalPoints = 0; + FOR_EACH_MAP( m_mapAchievement, i ) + { + CBaseAchievement *pAchievement = m_mapAchievement[i]; + + Msg( "%42s ", pAchievement->GetName() ); + + CFailableAchievement *pFailableAchievement = dynamic_cast<CFailableAchievement *>( pAchievement ); + if ( pAchievement->IsAchieved() ) + { + Msg( "%-20s", "ACHIEVED" ); + } + else if ( pFailableAchievement && pFailableAchievement->IsFailed() ) + { + Msg( "%-20s", "FAILED" ); + } + else + { + char szBuf[255]; + Q_snprintf( szBuf, ARRAYSIZE( szBuf ), "(%d/%d)%s", pAchievement->GetCount(), pAchievement->GetGoal(), + pAchievement->IsActive() ? "" : " (inactive)" ); + Msg( "%-20s", szBuf ); + } + Msg( " %d ", pAchievement->GetPointValue() ); + pAchievement->PrintAdditionalStatus(); + Msg( "\n" ); + iTotalAchievements++; + iTotalPoints += pAchievement->GetPointValue(); + } + Msg( "Total achievements: %d Total possible points: %d\n", iTotalAchievements, iTotalPoints ); +} + +//----------------------------------------------------------------------------- +// Purpose: called when a game event is fired +//----------------------------------------------------------------------------- +void CAchievementMgr::FireGameEvent( IGameEvent *event ) +{ + VPROF_( "CAchievementMgr::FireGameEvent", 1, VPROF_BUDGETGROUP_STEAM, false, 0 ); + const char *name = event->GetName(); + if ( name == NULL ) { return; } + if ( 0 == Q_strcmp( name, "entity_killed" ) ) + { +#ifdef GAME_DLL + CBaseEntity *pVictim = UTIL_EntityByIndex( event->GetInt( "entindex_killed", 0 ) ); + CBaseEntity *pAttacker = UTIL_EntityByIndex( event->GetInt( "entindex_attacker", 0 ) ); + CBaseEntity *pInflictor = UTIL_EntityByIndex( event->GetInt( "entindex_inflictor", 0 ) ); + OnKillEvent( pVictim, pAttacker, pInflictor, event ); +#endif // GAME_DLL + } + else if ( 0 == Q_strcmp( name, "game_init" ) ) + { +#ifdef GAME_DLL + // clear all state as though we were loading a saved game, but without loading the game + PreRestoreSavedGame(); + PostRestoreSavedGame(); +#endif // GAME_DLL + } +#ifdef CLIENT_DLL + else if ( 0 == Q_strcmp( name, "player_death" ) ) + { + CBaseEntity *pVictim = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("userid") ) ); + CBaseEntity *pAttacker = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("attacker") ) ); + OnKillEvent( pVictim, pAttacker, NULL, event ); + } + else if ( 0 == Q_strcmp( name, "localplayer_changeclass" ) ) + { + // keep track of when the player last changed class + m_flLastClassChangeTime = gpGlobals->curtime; + } + else if ( 0 == Q_strcmp( name, "localplayer_changeteam" ) ) + { + // keep track of the time of transitions to and from a game team + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + int iTeam = pLocalPlayer->GetTeamNumber(); + if ( iTeam > TEAM_SPECTATOR ) + { + if ( 0 == m_flTeamplayStartTime ) + { + // player transitioned from no/spectator team to a game team, mark the time + m_flTeamplayStartTime = gpGlobals->curtime; + } + } + else + { + // player transitioned to no/spectator team, clear the teamplay start time + m_flTeamplayStartTime = 0; + } + } + } + else if ( 0 == Q_strcmp( name, "teamplay_round_start" ) ) + { + if ( event->GetBool( "full_reset" ) ) + { + // we're starting a full round, clear miniround count + m_iMiniroundsCompleted = 0; + } + } + else if ( 0 == Q_strcmp( name, "teamplay_round_win" ) ) + { + if ( false == event->GetBool( "full_round", true ) ) + { + // we just finished a miniround but the round is continuing, increment miniround count + m_iMiniroundsCompleted ++; + } + } + else if ( 0 == Q_strcmp( name, "player_stats_updated" ) ) + { + FOR_EACH_MAP( m_mapAchievement, i ) + { + CBaseAchievement *pAchievement = m_mapAchievement[i]; + pAchievement->OnPlayerStatsUpdate(); + } + } +#endif // CLIENT_DLL +} + +//----------------------------------------------------------------------------- +// Purpose: called when a player or character has been killed +//----------------------------------------------------------------------------- +void CAchievementMgr::OnKillEvent( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event ) +{ + // can have a NULL victim on client if victim has never entered local player's PVS + if ( !pVictim ) + return; + + // if single-player game, calculate if the attacker is the local player and if the victim is the player enemy + bool bAttackerIsPlayer = false; + bool bVictimIsPlayerEnemy = false; +#ifdef GAME_DLL + if ( !g_pGameRules->IsMultiplayer() ) + { + CBasePlayer *pLocalPlayer = UTIL_GetLocalPlayer(); + if ( pLocalPlayer ) + { + if ( pAttacker == pLocalPlayer ) + { + bAttackerIsPlayer = true; + } + + CBaseCombatCharacter *pBCC = dynamic_cast<CBaseCombatCharacter *>( pVictim ); + if ( pBCC && ( D_HT == pBCC->IRelationType( pLocalPlayer ) ) ) + { + bVictimIsPlayerEnemy = true; + } + } + } +#else + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + bVictimIsPlayerEnemy = !pLocalPlayer->InSameTeam( pVictim ); + if ( pAttacker == pLocalPlayer ) + { + bAttackerIsPlayer = true; + } +#endif // GAME_DLL + + // look through all the kill event listeners and notify any achievements whose filters we pass + FOR_EACH_VEC( m_vecKillEventListeners, iAchievement ) + { + CBaseAchievement *pAchievement = m_vecKillEventListeners[iAchievement]; + + if ( !pAchievement->IsActive() ) + continue; + +#ifdef CLIENT_DLL + // Swallow kill events that can't be earned right now + if ( !pAchievement->LocalPlayerCanEarn() ) + continue; +#endif + + // if this achievement only looks for kills where attacker is player and that is not the case here, skip this achievement + if ( ( pAchievement->GetFlags() & ACH_FILTER_ATTACKER_IS_PLAYER ) && !bAttackerIsPlayer ) + continue; + + // if this achievement only looks for kills where victim is killer enemy and that is not the case here, skip this achievement + if ( ( pAchievement->GetFlags() & ACH_FILTER_VICTIM_IS_PLAYER_ENEMY ) && !bVictimIsPlayerEnemy ) + continue; + +#if GAME_DLL + // if this achievement only looks for a particular victim class name and this victim is a different class, skip this achievement + const char *pVictimClassNameFilter = pAchievement->m_pVictimClassNameFilter; + if ( pVictimClassNameFilter && !pVictim->ClassMatches( pVictimClassNameFilter ) ) + continue; + + // if this achievement only looks for a particular inflictor class name and this inflictor is a different class, skip this achievement + const char *pInflictorClassNameFilter = pAchievement->m_pInflictorClassNameFilter; + if ( pInflictorClassNameFilter && ( ( NULL == pInflictor ) || !pInflictor->ClassMatches( pInflictorClassNameFilter ) ) ) + continue; + + // if this achievement only looks for a particular attacker class name and this attacker is a different class, skip this achievement + const char *pAttackerClassNameFilter = pAchievement->m_pAttackerClassNameFilter; + if ( pAttackerClassNameFilter && ( ( NULL == pAttacker ) || !pAttacker->ClassMatches( pAttackerClassNameFilter ) ) ) + continue; + + // if this achievement only looks for a particular inflictor entity name and this inflictor has a different name, skip this achievement + const char *pInflictorEntityNameFilter = pAchievement->m_pInflictorEntityNameFilter; + if ( pInflictorEntityNameFilter && ( ( NULL == pInflictor ) || !pInflictor->NameMatches( pInflictorEntityNameFilter ) ) ) + continue; +#endif // GAME_DLL + + // we pass all filters for this achievement, notify the achievement of the kill + pAchievement->Event_EntityKilled( pVictim, pAttacker, pInflictor, event ); + } +} + +void CAchievementMgr::OnAchievementEvent( int iAchievementID, int iCount ) +{ + // have we loaded the achievements yet? + if ( m_mapAchievement.Count() ) + { + // handle event for specific achievement + CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID ); + Assert( pAchievement ); + if ( pAchievement ) + { + if ( !pAchievement->IsAchieved() ) + { + pAchievement->IncrementCount( iCount ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when a map-fired achievement event occurs +//----------------------------------------------------------------------------- +void CAchievementMgr::OnMapEvent( const char *pchEventName ) +{ + Assert( pchEventName && *pchEventName ); + if ( !pchEventName || !*pchEventName ) + return; + + // see if this event matches the prefix for an achievement component + FOR_EACH_VEC( m_vecComponentListeners, iAchievement ) + { + CBaseAchievement *pAchievement = m_vecComponentListeners[iAchievement]; + Assert( pAchievement->m_pszComponentPrefix ); + if ( 0 == Q_strncmp( pchEventName, pAchievement->m_pszComponentPrefix, pAchievement->m_iComponentPrefixLen ) ) + { + // prefix matches, tell the achievement a component was found + pAchievement->OnComponentEvent( pchEventName ); + return; + } + } + + // look through all the map event listeners + FOR_EACH_VEC( m_vecMapEventListeners, iAchievement ) + { + CBaseAchievement *pAchievement = m_vecMapEventListeners[iAchievement]; + pAchievement->OnMapEvent( pchEventName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns an achievement as it's abstract object. This interface is used by gameui.dll for getting achievement info. +// Input : index - +// Output : IBaseAchievement* +//----------------------------------------------------------------------------- +IAchievement* CAchievementMgr::GetAchievementByIndex( int index ) +{ + Assert( m_vecAchievement.IsValidIndex(index) ); + return (IAchievement*)m_vecAchievement[index]; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns total achievement count. This interface is used by gameui.dll for getting achievement info. +// Input : - +// Output : Count of achievements in manager's vector. +//----------------------------------------------------------------------------- +int CAchievementMgr::GetAchievementCount() +{ + return m_vecAchievement.Count(); +} + + +#if !defined(NO_STEAM) +//----------------------------------------------------------------------------- +// Purpose: called when stat download is complete +//----------------------------------------------------------------------------- +void CAchievementMgr::Steam_OnUserStatsReceived( UserStatsReceived_t *pUserStatsReceived ) +{ + Assert( steamapicontext->SteamUserStats() ); + if ( !steamapicontext->SteamUserStats() ) + return; + + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "CAchievementMgr::Steam_OnUserStatsReceived: result = %i\n", pUserStatsReceived->m_eResult ); + } + + if ( pUserStatsReceived->m_eResult != k_EResultOK ) + { + DevMsg( "CTFSteamStats: failed to download stats from Steam, EResult %d\n", pUserStatsReceived->m_eResult ); + return; + } + + UpdateStateFromSteam_Internal(); +} + +//----------------------------------------------------------------------------- +// Purpose: called when stat upload is complete +//----------------------------------------------------------------------------- +void CAchievementMgr::Steam_OnUserStatsStored( UserStatsStored_t *pUserStatsStored ) +{ + if ( cc_achievement_debug.GetInt() > 0 ) + { + Msg( "CAchievementMgr::Steam_OnUserStatsStored: result = %i\n", pUserStatsStored->m_eResult ); + } + + if ( k_EResultOK != pUserStatsStored->m_eResult && k_EResultInvalidParam != pUserStatsStored->m_eResult ) + { + // We had some failure (like not connected or timeout) that means the stats were not stored. Redirty to try again + SetDirty( true ); + } + else + { + if ( k_EResultInvalidParam == pUserStatsStored->m_eResult ) + { + // for whatever reason, stats and achievements were rejected by Steam. In order to remain in + // synch, we get the current values/state from Steam. + UpdateStateFromSteam_Internal(); + + // In the case of k_EResultInvalidParam, some of the stats may have been stored, so we should still fall through to + // fire events related to achievements being awarded. + } + + while ( m_AchievementsAwarded.Count() > 0 ) + { +#ifndef GAME_DLL + // send a message to the server about our achievement + if ( g_pGameRules && g_pGameRules->IsMultiplayer() ) + { + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer ) + { + int nAchievementID = m_AchievementsAwarded[0]; + CBaseAchievement* pAchievement = GetAchievementByID( nAchievementID ); + + // verify that it is still achieved (it could have been rejected by Steam) + if ( pAchievement->IsAchieved() ) + { + // Get the unlocked time from Steam + uint32 unlockTime; + bool bAchieved; + bool bRet = steamapicontext->SteamUserStats()->GetAchievementAndUnlockTime( pAchievement->GetName(), &bAchieved, &unlockTime ); + if ( bRet && bAchieved ) + { + // set the unlock time + pAchievement->SetUnlockTime( unlockTime ); + } + + KeyValues *kv = new KeyValues( "AchievementEarned" ); + kv->SetInt( "achievementID", nAchievementID ); + engine->ServerCmdKeyValues( kv ); + } + } + } +#endif + m_AchievementsAwarded.Remove( 0 ); + } + + CheckMetaAchievements(); + } +} +#endif // !defined(NO_STEAM) + +void CAchievementMgr::CheckMetaAchievements( void ) +{ + // Check the meta completion achievements. + // Only one iteration over the achievements set is necessary. + FOR_EACH_MAP( m_mapMetaAchievement, iAchievement ) + { + CAchievement_AchievedCount* pMetaAchievement = m_mapMetaAchievement[iAchievement]; + if ( !pMetaAchievement || pMetaAchievement->IsAchieved() ) + continue; + + int iAchieved = 0; + for ( int i=pMetaAchievement->GetLowRange(); i<=pMetaAchievement->GetHighRange(); i++ ) + { + CBaseAchievement* pAchievement = GetAchievementByID( i ); + if ( pAchievement && pAchievement->IsAchieved() ) + { + iAchieved++; + } + } + if ( iAchieved >= pMetaAchievement->GetNumRequired() ) + { + pMetaAchievement->IncrementCount(); + } + } +} + +void CAchievementMgr::ResetAchievement_Internal( CBaseAchievement *pAchievement ) +{ + Assert( pAchievement ); + +#ifndef NO_STEAM + if ( steamapicontext->SteamUserStats() ) + { + steamapicontext->SteamUserStats()->ClearAchievement( pAchievement->GetName() ); + } +#endif + pAchievement->SetAchieved( false ); + pAchievement->SetCount( 0 ); + if ( pAchievement->HasComponents() ) + { + pAchievement->SetComponentBits( 0 ); + } + pAchievement->SetProgressShown( 0 ); + pAchievement->StopListeningForAllEvents(); + if ( pAchievement->IsActive() ) + { + pAchievement->ListenForEvents(); + } +} + +void CAchievementMgr::SetAchievementThink( CBaseAchievement *pAchievement, float flThinkTime ) +{ + // Is the achievement already in the think list? + int iCount = m_vecThinkListeners.Count(); + for ( int i = 0; i < iCount; i++ ) + { + if ( m_vecThinkListeners[i].pAchievement == pAchievement ) + { + if ( flThinkTime == THINK_CLEAR ) + { + m_vecThinkListeners.Remove(i); + return; + } + + m_vecThinkListeners[i].m_flThinkTime = gpGlobals->curtime + flThinkTime; + return; + } + } + + if ( flThinkTime == THINK_CLEAR ) + return; + + // Otherwise, add it to the list + int iIdx = m_vecThinkListeners.AddToTail(); + m_vecThinkListeners[iIdx].pAchievement = pAchievement; + m_vecThinkListeners[iIdx].m_flThinkTime = gpGlobals->curtime + flThinkTime; +} + +void CAchievementMgr::UpdateStateFromSteam_Internal() +{ +#ifndef NO_STEAM + // run through the achievements and set their achieved state according to Steam data + FOR_EACH_MAP( m_mapAchievement, i ) + { + CBaseAchievement *pAchievement = m_mapAchievement[i]; + bool bAchieved = false; + + uint32 unlockTime; + + // Get the achievement status, and the time it was unlocked if unlocked. + // If the return value is true, but the unlock time is zero, that means it was unlocked before Steam + // began tracking achievement unlock times (December 2009). Time is seconds since January 1, 1970. + bool bRet = steamapicontext->SteamUserStats()->GetAchievementAndUnlockTime( pAchievement->GetName(), &bAchieved, &unlockTime ); + + if ( bRet ) + { + // set local achievement state + pAchievement->SetAchieved( bAchieved ); + pAchievement->SetUnlockTime(unlockTime); + } + else + { + DevMsg( "ISteamUserStats::GetAchievement failed for %s\n", pAchievement->GetName() ); + } + + if ( pAchievement->StoreProgressInSteam() ) + { + int iValue; + char pszProgressName[1024]; + Q_snprintf( pszProgressName, 1024, "%s_STAT", pAchievement->GetStat() ); + bRet = steamapicontext->SteamUserStats()->GetStat( pszProgressName, &iValue ); + if ( bRet ) + { + pAchievement->SetCount( iValue ); + pAchievement->EvaluateNewAchievement(); + } + else + { + DevMsg( "ISteamUserStats::GetStat failed to get progress value from Steam for achievement %s\n", pszProgressName ); + } + } + } + + // send an event to anyone else who needs Steam user stat data + IGameEvent *event = gameeventmanager->CreateEvent( "user_data_downloaded" ); + if ( event ) + { +#ifdef GAME_DLL + gameeventmanager->FireEvent( event ); +#else + gameeventmanager->FireEventClientSide( event ); +#endif + } +#endif +} + +#ifdef CLIENT_DLL + +void MsgFunc_AchievementEvent( bf_read &msg ) +{ + int iAchievementID = (int) msg.ReadShort(); + int iCount = (int) msg.ReadShort(); + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + pAchievementMgr->OnAchievementEvent( iAchievementID, iCount ); +} + +#if defined(_DEBUG) || defined(STAGING_ONLY) || DEBUG_ACHIEVEMENTS_IN_RELEASE +CON_COMMAND_F( achievement_reset_all, "Clears all achievements", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + pAchievementMgr->ResetAchievements(); +} + +CON_COMMAND_F( achievement_reset, "<internal name> Clears specified achievement", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + + if ( 2 != args.ArgC() ) + { + Msg( "Usage: achievement_reset <internal name>\n" ); + return; + } + CBaseAchievement *pAchievement = pAchievementMgr->GetAchievementByName( args[1] ); + if ( !pAchievement ) + { + Msg( "Achievement %s not found\n", args[1] ); + return; + } + pAchievementMgr->ResetAchievement( pAchievement->GetAchievementID() ); + +} + +CON_COMMAND_F( achievement_status, "Shows status of all achievement", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + pAchievementMgr->PrintAchievementStatus(); +} + +CON_COMMAND_F( achievement_unlock, "<internal name> Unlocks achievement", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + + if ( 2 != args.ArgC() ) + { + Msg( "Usage: achievement_unlock <internal name>\n" ); + return; + } + CBaseAchievement *pAchievement = pAchievementMgr->GetAchievementByName( args[1] ); + if ( !pAchievement ) + { + Msg( "Achievement %s not found\n", args[1] ); + return; + } + pAchievementMgr->AwardAchievement( pAchievement->GetAchievementID() ); +} + +CON_COMMAND_F( achievement_unlock_all, "Unlocks all achievements", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + + int iCount = pAchievementMgr->GetAchievementCount(); + for ( int i = 0; i < iCount; i++ ) + { + IAchievement *pAchievement = pAchievementMgr->GetAchievementByIndex( i ); + if ( !pAchievement->IsAchieved() ) + { + pAchievementMgr->AwardAchievement( pAchievement->GetAchievementID() ); + } + } +} + +CON_COMMAND_F( achievement_evaluate, "<internal name> Causes failable achievement to be evaluated", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + + if ( 2 != args.ArgC() ) + { + Msg( "Usage: achievement_evaluate <internal name>\n" ); + return; + } + CBaseAchievement *pAchievement = pAchievementMgr->GetAchievementByName( args[1] ); + if ( !pAchievement ) + { + Msg( "Achievement %s not found\n", args[1] ); + return; + } + + CFailableAchievement *pFailableAchievement = dynamic_cast<CFailableAchievement *>( pAchievement ); + if ( !pFailableAchievement ) + { + Msg( "Achievement %s is not failable\n", args[1] ); + return; + } + + pFailableAchievement->OnEvaluationEvent(); +} + +CON_COMMAND_F( achievement_test_friend_count, "Counts the # of teammates on local player's friends list", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + if ( 2 != args.ArgC() ) + { + Msg( "Usage: achievement_test_friend_count <min # of teammates>\n" ); + return; + } + int iMinFriends = atoi( args[1] ); + bool bRet = CalcPlayersOnFriendsList( iMinFriends ); + Msg( "You %s have at least %d friends in the game.\n", bRet ? "do" : "do not", iMinFriends ); +} + +CON_COMMAND_F( achievement_test_clan_count, "Determines if specified # of teammates belong to same clan w/local player", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + + if ( 2 != args.ArgC() ) + { + Msg( "Usage: achievement_test_clan_count <# of clan teammates>\n" ); + return; + } + int iClanPlayers = atoi( args[1] ); + bool bRet = CalcHasNumClanPlayers( iClanPlayers ); + Msg( "There %s %d players who you're in a Steam group with.\n", bRet ? "are" : "are not", iClanPlayers ); +} + +CON_COMMAND_F( achievement_mark_dirty, "Mark achievement data as dirty", FCVAR_CHEAT ) +{ + CAchievementMgr *pAchievementMgr = dynamic_cast<CAchievementMgr *>( engine->GetAchievementMgr() ); + if ( !pAchievementMgr ) + return; + pAchievementMgr->SetDirty( true ); +} +#endif // _DEBUG + +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: helper function to get entity model name +//----------------------------------------------------------------------------- +const char *GetModelName( CBaseEntity *pBaseEntity ) +{ + CBaseAnimating *pBaseAnimating = dynamic_cast<CBaseAnimating *>( pBaseEntity ); + if ( pBaseAnimating ) + { + CStudioHdr *pStudioHdr = pBaseAnimating->GetModelPtr(); + if ( pStudioHdr ) + { + return pStudioHdr->pszName(); + } + } + + return ""; +} |