diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/shared/baseachievement.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/shared/baseachievement.cpp')
| -rw-r--r-- | mp/src/game/shared/baseachievement.cpp | 1494 |
1 files changed, 747 insertions, 747 deletions
diff --git a/mp/src/game/shared/baseachievement.cpp b/mp/src/game/shared/baseachievement.cpp index 82db3c32..494133c1 100644 --- a/mp/src/game/shared/baseachievement.cpp +++ b/mp/src/game/shared/baseachievement.cpp @@ -1,748 +1,748 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-//=============================================================================
-
-#include "cbase.h"
-#include "achievementmgr.h"
-#include "icommandline.h"
-#ifdef CLIENT_DLL
-#include "tier3/tier3.h"
-#include "vgui/ILocalize.h"
-#include "achievement_notification_panel.h"
-#include "fmtstr.h"
-#include "gamestats.h"
-#endif // CLIENT_DLL
-
-//=============================================================================
-// HPE_BEGIN
-// [dwenger] Necessary for sorting achievements by award time
-//=============================================================================
-
-#include <vgui/ISystem.h>
-#include "vgui_controls/Controls.h"
-
-//=============================================================================
-// HPE_END
-//=============================================================================
-
-CBaseAchievementHelper *CBaseAchievementHelper::s_pFirst = NULL;
-
-BEGIN_DATADESC_NO_BASE( CBaseAchievement )
-DEFINE_FIELD( m_iCount, FIELD_INTEGER ),
-END_DATADESC()
-
-BEGIN_DATADESC( CFailableAchievement )
-DEFINE_FIELD( m_bActivated, FIELD_BOOLEAN ),
-DEFINE_FIELD( m_bFailed, FIELD_BOOLEAN ),
-END_DATADESC()
-
-//-----------------------------------------------------------------------------
-// Purpose: constructor
-//-----------------------------------------------------------------------------
-CBaseAchievement::CBaseAchievement()
-{
- m_iFlags = 0;
- m_iGoal = 0;
- m_iProgressMsgIncrement = 0;
- m_iProgressMsgMinimum = 0;
- m_iAchievementID = 0;
- m_iPointValue = 0;
- m_bHideUntilAchieved = false;
- m_bStoreProgressInSteam = false;
- m_pVictimClassNameFilter = NULL;
- m_pAttackerClassNameFilter = NULL;
- m_pInflictorClassNameFilter = NULL;
- m_pInflictorEntityNameFilter = NULL;
- m_pMapNameFilter = NULL;
- m_pGameDirFilter = NULL;
- m_pszComponentNames = NULL;
- m_pszComponentPrefix = NULL;
- m_iNumComponents = 0;
- m_iComponentPrefixLen = 0;
- m_iComponentBits = 0;
- m_iCount = 0;
- m_iProgressShown = 0;
- m_bAchieved = false;
- m_uUnlockTime = 0;
- m_pAchievementMgr = NULL;
- m_bShowOnHUD = false;
- m_pszStat = NULL;
-}
-
-CBaseAchievement::~CBaseAchievement()
-{
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets flags
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetFlags( int iFlags )
-{
- // must always specify a save method
- Assert( iFlags & ( ACH_SAVE_WITH_GAME | ACH_SAVE_GLOBAL ) );
-
- m_iFlags = iFlags;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called when a game event being listened for is dispatched
-//-----------------------------------------------------------------------------
-void CBaseAchievement::FireGameEvent( IGameEvent *event )
-{
- // perform common filtering to make it simpler to write achievements
- if ( !IsActive() )
- return;
-
- // if the achievement only applies to a specific map, and it's not the current map, skip it
- if ( m_pMapNameFilter && ( 0 != Q_strcmp( m_pAchievementMgr->GetMapName(), m_pMapNameFilter ) ) )
- return;
-
- const char *name = event->GetName();
- if ( 0 == Q_strcmp( name, "teamplay_round_win" ) )
- {
- // if this is a round win and the achievement wants full round events only, filter this out
- // if this is not the end of a full round
- if ( ( m_iFlags & ACH_FILTER_FULL_ROUND_ONLY ) && ( false == event->GetBool( "full_round" ) ) )
- return;
- }
-
- // let the achievement handle the event
- FireGameEvent_Internal( event );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: sets victim class to filter with
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetVictimFilter( const char *pClassName )
-{
- m_pVictimClassNameFilter = pClassName;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets attacker class to filter with
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetAttackerFilter( const char *pClassName )
-{
- m_pAttackerClassNameFilter = pClassName;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets inflictor class to filter with
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetInflictorFilter( const char *pClassName )
-{
- m_pInflictorClassNameFilter = pClassName;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets inflictor entity name to filter with
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetInflictorEntityNameFilter( const char *pEntityName )
-{
- m_pInflictorEntityNameFilter = pEntityName;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets map name to filter with
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetMapNameFilter( const char *pMapName )
-{
- m_pMapNameFilter = pMapName;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets game dir to filter with. Note: in general, achievements should
-// only be compiled into products they pertain to. But if there are
-// any game-specific achievements which need to be in a binary shared
-// across products (e.g. Ep1 & Ep2), use the game dir as a runtime
-// filter.
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetGameDirFilter( const char *pGameDir )
-{
- m_pGameDirFilter = pGameDir;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets prefix to look for in map event string to identify a component
-// for this achievement
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetComponentPrefix( const char *pPrefix )
-{
- m_pszComponentPrefix = pPrefix;
- m_iComponentPrefixLen = Q_strlen( pPrefix );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called when a kill that passes filter critera occurs. This
-// is the default implementation, achievements can override to
-// do special handling
-//-----------------------------------------------------------------------------
-void CBaseAchievement::Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event )
-{
- // extra paranoid check: should only get here if registered as a kill event listener
- Assert( GetFlags() & ACH_LISTEN_KILL_EVENTS );
- if ( !( GetFlags() & ACH_LISTEN_KILL_EVENTS ) )
- return;
-
- // default implementation is just to increase count when filter criteria pass
- // Msg( "Base achievement incremented on kill event.\n" );
- IncrementCount();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called when an event that counts toward an achievement occurs
-//-----------------------------------------------------------------------------
-void CBaseAchievement::IncrementCount( int iOptIncrement )
-{
- if ( !IsAchieved() && LocalPlayerCanEarn() )
- {
- if ( !AlwaysEnabled() && !m_pAchievementMgr->CheckAchievementsEnabled() )
- {
- Msg( "Achievements disabled, ignoring achievement progress for %s\n", GetName() );
- return;
- }
-
- // on client, where the count is kept, increment count
- if ( iOptIncrement > 0 )
- {
- // user specified that we want to increase by more than one.
- m_iCount += iOptIncrement;
- if ( m_iCount > m_iGoal )
- {
- m_iCount = m_iGoal;
- }
- }
- else
- {
- m_iCount++;
- }
-
- // if this achievement gets saved w/global state, flag our global state as dirty
- if ( GetFlags() & ACH_SAVE_GLOBAL )
- {
- m_pAchievementMgr->SetDirty( true );
- }
-
- if ( cc_achievement_debug.GetInt() )
- {
- Msg( "Achievement count increased for %s: %d/%d\n", GetName(), m_iCount, m_iGoal );
- }
-
-#ifndef NO_STEAM
- // if this achievement's progress should be stored in Steam, set the steam stat for it
- if ( StoreProgressInSteam() && steamapicontext->SteamUserStats() )
- {
- // Set the Steam stat with the same name as the achievement. Only cached locally until we upload it.
- char pszProgressName[1024];
- Q_snprintf( pszProgressName, 1024, "%s_STAT", GetStat() );
- bool bRet = steamapicontext->SteamUserStats()->SetStat( pszProgressName, m_iCount );
- if ( !bRet )
- {
- DevMsg( "ISteamUserStats::GetStat failed to set progress value in Steam for achievement %s\n", pszProgressName );
- }
-
- m_pAchievementMgr->SetDirty( true );
- }
-#endif
- // if we've hit goal, award the achievement
- if ( m_iGoal > 0 )
- {
- if ( m_iCount >= m_iGoal )
- {
- AwardAchievement();
- }
- else
- {
- HandleProgressUpdate();
- }
- }
-
- }
-}
-
-void CBaseAchievement::SetShowOnHUD( bool bShow )
-{
- if ( m_bShowOnHUD != bShow )
- {
- m_pAchievementMgr->SetDirty( true );
- }
-
- m_bShowOnHUD = bShow;
-}
-
-void CBaseAchievement::HandleProgressUpdate()
-{
- // if we've hit the right # of progress steps to show a progress notification, show it
- if ( ( m_iProgressMsgIncrement > 0 ) && m_iCount >= m_iProgressMsgMinimum && ( 0 == ( m_iCount % m_iProgressMsgIncrement ) ) )
- {
- // which notification is this
- int iProgress = m_iCount / m_iProgressMsgIncrement;
- // if we haven't already shown this progress step, show it
- if ( iProgress > m_iProgressShown )
- {
- ShowProgressNotification();
- // remember progress step shown so we don't show it again if the player loads an earlier save game
- // and gets past this point again
- m_iProgressShown = iProgress;
- m_pAchievementMgr->SetDirty( true );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: calculates at how many steps we should show a progress notification
-//-----------------------------------------------------------------------------
-void CBaseAchievement::CalcProgressMsgIncrement()
-{
- // by default, show progress at every 25%
- m_iProgressMsgIncrement = m_iGoal / 4;
- // if goal is not evenly divisible by 4, try some other values
- if ( 0 != ( m_iGoal % 4 ) )
- {
- if ( 0 == ( m_iGoal % 3 ) )
- {
- // if evenly divisible by 3, use that
- m_iProgressMsgIncrement = m_iGoal / 3;
- }
- else if ( 0 == ( m_iGoal % 5 ) )
- {
- // if evenly divisible by 5, use that
- m_iProgressMsgIncrement = m_iGoal / 5;
- }
- // otherwise stick with divided by 4, rounded off
- }
-
- // don't show progress notifications for less than 5 things
- if ( m_iProgressMsgIncrement < 5 )
- {
- m_iProgressMsgIncrement = 0;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetNextThink( float flThinkTime )
-{
- m_pAchievementMgr->SetAchievementThink( this, flThinkTime );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CBaseAchievement::ClearThink( void )
-{
- m_pAchievementMgr->SetAchievementThink( this, THINK_CLEAR );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: see if we should award an achievement based on what just happened
-//-----------------------------------------------------------------------------
-void CBaseAchievement::EvaluateNewAchievement()
-{
- if ( !IsAchieved() && m_iGoal > 0 && m_iCount >= m_iGoal )
- {
- AwardAchievement();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: determine if we should set this achievement to be achieved based
-// on other state. Used at init time.
-//-----------------------------------------------------------------------------
-void CBaseAchievement::EvaluateIsAlreadyAchieved()
-{
- if ( !IsAchieved() && m_iGoal > 0 && m_iCount >= m_iGoal )
- {
- m_bAchieved = true;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called a map event for this achievement occurs
-//-----------------------------------------------------------------------------
-void CBaseAchievement::OnMapEvent( const char *pEventName )
-{
- Assert( m_iFlags & ACH_LISTEN_MAP_EVENTS );
-
- if ( 0 == Q_stricmp( pEventName, GetName() ) )
- {
- IncrementCount();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called when an achievement is awarded
-//-----------------------------------------------------------------------------
-void CBaseAchievement::AwardAchievement()
-{
- Assert( !IsAchieved() );
- if ( IsAchieved() )
- return;
-
- m_pAchievementMgr->AwardAchievement( m_iAchievementID );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called when a component of a multi-component event is found
-//-----------------------------------------------------------------------------
-void CBaseAchievement::OnComponentEvent( const char *pchComponentName )
-{
- // find the component name in our list
- for ( int i = 0; i < m_iNumComponents; i++ )
- {
- if ( 0 == Q_strcmp( pchComponentName, m_pszComponentNames[i] ) )
- {
- EnsureComponentBitSetAndEvaluate( i );
- return;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets the specified component bit # if it is not already.
-// If it does get set, evaluate if this satisfies an achievement
-//-----------------------------------------------------------------------------
-void CBaseAchievement::EnsureComponentBitSetAndEvaluate( int iBitNumber )
-{
- Assert( iBitNumber < 64 ); // this is bit #, not a bit mask
-
- if ( IsAchieved() )
- return;
-
- // calculate which bit this component corresponds to
- uint64 iBitMask = ( (uint64) 1 ) << iBitNumber;
-
- // see if we already have gotten this component
- if ( 0 == ( iBitMask & m_iComponentBits ) )
- {
- if ( !AlwaysEnabled() && !m_pAchievementMgr->CheckAchievementsEnabled() )
- {
- Msg( "Achievements disabled, ignoring achievement component for %s\n", GetName() );
- return;
- }
-
- // new component, set the bit and increment the count
- SetComponentBits( m_iComponentBits | iBitMask );
- Assert( m_iCount <= m_iGoal );
- if ( m_iCount == m_iGoal )
- {
- // all components found, award the achievement (and save state)
- AwardAchievement();
- }
- else
- {
- // save our state at the next good opportunity
- m_pAchievementMgr->SetDirty( true );
-
- if ( cc_achievement_debug.GetInt() )
- {
- Msg( "Component %d for achievement %s found\n", iBitNumber, GetName() );
- }
-
- ShowProgressNotification();
- }
- }
- else
- {
- if ( cc_achievement_debug.GetInt() )
- {
- Msg( "Component %d for achievement %s found, but already had that component\n", iBitNumber, GetName() );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: displays achievement progress notification in the HUD
-//-----------------------------------------------------------------------------
-void CBaseAchievement::ShowProgressNotification()
-{
- if ( !ShouldShowProgressNotification() )
- return;
-
- IGameEvent *event = gameeventmanager->CreateEvent( "achievement_event" );
- if ( event )
- {
- event->SetString( "achievement_name", GetName() );
- event->SetInt( "cur_val", m_iCount );
- event->SetInt( "max_val", m_iGoal );
-#ifdef GAME_DLL
- gameeventmanager->FireEvent( event );
-#else
- gameeventmanager->FireEventClientSide( event );
-#endif
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: clears dynamic state for this achievement
-//-----------------------------------------------------------------------------
-void CBaseAchievement::PreRestoreSavedGame()
-{
- // if this achievement gets saved with the game, clear its state
- if ( m_iFlags & ACH_SAVE_WITH_GAME )
- {
- m_iCount = 0;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called after the data in this achievement has been restored from saved game
-//-----------------------------------------------------------------------------
-void CBaseAchievement::PostRestoreSavedGame()
-{
- EvaluateIsAlreadyAchieved();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets component bits for this achievement
-//-----------------------------------------------------------------------------
-void CBaseAchievement::SetComponentBits( uint64 iComponentBits )
-{
- Assert( m_iFlags & ACH_HAS_COMPONENTS );
- // set the bit field
- m_iComponentBits = iComponentBits;
- // count how many bits are set and save that as the count
- int iNumBitsSet = 0;
- while ( iComponentBits > 0 )
- {
- if ( iComponentBits & 1 )
- {
- iNumBitsSet++;
- }
- iComponentBits >>= 1;
- }
- m_iCount = iNumBitsSet;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns whether we should save this achievement with a save game
-//-----------------------------------------------------------------------------
-bool CBaseAchievement::ShouldSaveWithGame()
-{
- // save if we should get saved with the game, have a non-zero count, and have not
- // been achieved (at which point the achievement state gets saved globally)
- return ( ( m_iFlags & ACH_SAVE_WITH_GAME ) > 0 && ( GetCount() > 0 ) && !IsAchieved() );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns whether we should save this achievement to the global file
-//-----------------------------------------------------------------------------
-bool CBaseAchievement::ShouldSaveGlobal()
-{
- // save if we should get saved globally and have a non-zero count, or if we have been achieved, or if the player has pinned this achievement to the HUD
- return ( ( ( m_iFlags & ACH_SAVE_GLOBAL ) > 0 && ( GetCount() > 0 ) ) || IsAchieved() || ( m_iProgressShown > 0 ) || ShouldShowOnHUD() );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns whether this achievement is active
-//-----------------------------------------------------------------------------
-bool CBaseAchievement::IsActive()
-{
- // we're not active if already achieved
- if ( IsAchieved() )
- return false;
-
- // if there's a map filter and we're not on the specified map, we're not active
- if ( ( m_pMapNameFilter ) && ( 0 != Q_strcmp( m_pAchievementMgr->GetMapName(), m_pMapNameFilter ) ) )
- return false;
-
- return true;
-}
-
-//=============================================================================
-// HPE_BEGIN:
-// [pfreese] Moved serialization from AchievementMgr to here so that derived
-// classes can persist additional data
-//=============================================================================
-
-//-----------------------------------------------------------------------------
-// Purpose: Serialize our data to the KeyValues node
-//-----------------------------------------------------------------------------
-void CBaseAchievement::GetSettings( KeyValues *pNodeOut )
-{
- pNodeOut->SetInt( "value", IsAchieved() ? 1 : 0 );
-
- if ( HasComponents() )
- {
- pNodeOut->SetUint64( "data", m_iComponentBits );
- }
- else
- {
- if ( !IsAchieved() )
- {
- pNodeOut->SetInt( "data", m_iCount );
- }
- }
- pNodeOut->SetInt( "hud", ShouldShowOnHUD() ? 1 : 0 );
- pNodeOut->SetInt( "msg", m_iProgressShown );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Unserialize our data from the KeyValues node
-//-----------------------------------------------------------------------------
-void CBaseAchievement::ApplySettings( KeyValues *pNodeIn )
-{
- // set the count
- if ( pNodeIn->GetInt( "value" ) > 0 )
- {
- m_iCount = m_iGoal;
- m_bAchieved = true;
- }
- else if ( !HasComponents() )
- {
- m_iCount = pNodeIn->GetInt( "data" );
- }
-
- // if this achievement has components, set the component bits
- if ( HasComponents() )
- {
- int64 iComponentBits = pNodeIn->GetUint64( "data" );
- SetComponentBits( iComponentBits );
- }
- SetShowOnHUD( !!pNodeIn->GetInt( "hud" ) );
- m_iProgressShown = pNodeIn->GetInt( "msg" );
-}
-
-//=============================================================================
-// HPE_END
-//=============================================================================
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-//-----------------------------------------------------------------------------
-CFailableAchievement::CFailableAchievement() : CBaseAchievement()
-{
- m_bFailed = false;
- m_bActivated = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns whether we should save this achievement with a save game
-//-----------------------------------------------------------------------------
-bool CFailableAchievement::ShouldSaveWithGame()
-{
- // save if we should get saved with the game, and are active or have failed
- return ( ( ( m_iFlags & ACH_SAVE_WITH_GAME ) > 0 ) && ( m_bActivated || m_bFailed ) );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: clears dynamic state for this achievement
-//-----------------------------------------------------------------------------
-void CFailableAchievement::PreRestoreSavedGame()
-{
- m_bFailed = false;
- m_bActivated = false;
-
- BaseClass::PreRestoreSavedGame();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: called after the data in this achievement has been restored from saved game
-//-----------------------------------------------------------------------------
-void CFailableAchievement::PostRestoreSavedGame()
-{
- // if there is no activation event set for this achievement, it is always active, activate it now
- if ( !m_bFailed && !GetActivationEventName()[0] )
- {
- m_bActivated = true;
- }
-
- if ( m_bActivated )
- {
- Activate();
- }
-
- BaseClass::PostRestoreSavedGame();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: called when a map event occurs
-//-----------------------------------------------------------------------------
-void CFailableAchievement::OnMapEvent( const char *pEventName )
-{
- // if we're not activated and we got the activation event, activate
- if ( !m_bActivated && ( 0 == Q_stricmp( pEventName, GetActivationEventName() ) ) )
- {
- OnActivationEvent();
- }
- // if this is the evaluation event, see if we've failed or not
- else if ( m_bActivated && 0 == Q_stricmp( pEventName, GetEvaluationEventName() ) )
- {
- OnEvaluationEvent();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when this failable achievement is activated
-//-----------------------------------------------------------------------------
-void CFailableAchievement::Activate()
-{
- m_bActivated = true;
- ListenForEvents();
- if ( cc_achievement_debug.GetInt() )
- {
- Msg( "Failable achievement %s now active\n", GetName() );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when this failable achievement should be evaluated
-//-----------------------------------------------------------------------------
-void CFailableAchievement::OnEvaluationEvent()
-{
- if ( !m_bFailed )
- {
- // we haven't failed and we reached the evaluation point, we've succeeded
- IncrementCount();
- }
-
- if ( cc_achievement_debug.GetInt() )
- {
- Msg( "Failable achievement %s has been evaluated (%s), now inactive\n", GetName(), m_bFailed ? "FAILED" : "AWARDED" );
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets this achievement to failed
-//-----------------------------------------------------------------------------
-void CFailableAchievement::SetFailed()
-{
- if ( !m_bFailed )
- {
- m_bFailed = true;
-
- if ( cc_achievement_debug.GetInt() )
- {
- Msg( "Achievement failed: %s (%s)\n", GetName(), GetName() );
- }
- }
-}
-
-//===========================================
-
-void CAchievement_AchievedCount::Init()
-{
- SetFlags( ACH_SAVE_GLOBAL );
- SetGoal( 1 );
- SetAchievementsRequired( 0, 0, 0 );
-}
-
-// Count how many achievements have been earned in our range
-void CAchievement_AchievedCount::OnSteamUserStatsStored( void )
-{
- // DO NO CALL. REPLACED BY CHECKMETAACHIEVEMENTS!
- Assert( 0 );
-}
-
-void CAchievement_AchievedCount::SetAchievementsRequired( int iNumRequired, int iLowRange, int iHighRange )
-{
- m_iNumRequired = iNumRequired;
- m_iLowRange = iLowRange;
- m_iHighRange = iHighRange;
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "achievementmgr.h" +#include "icommandline.h" +#ifdef CLIENT_DLL +#include "tier3/tier3.h" +#include "vgui/ILocalize.h" +#include "achievement_notification_panel.h" +#include "fmtstr.h" +#include "gamestats.h" +#endif // CLIENT_DLL + +//============================================================================= +// HPE_BEGIN +// [dwenger] Necessary for sorting achievements by award time +//============================================================================= + +#include <vgui/ISystem.h> +#include "vgui_controls/Controls.h" + +//============================================================================= +// HPE_END +//============================================================================= + +CBaseAchievementHelper *CBaseAchievementHelper::s_pFirst = NULL; + +BEGIN_DATADESC_NO_BASE( CBaseAchievement ) +DEFINE_FIELD( m_iCount, FIELD_INTEGER ), +END_DATADESC() + +BEGIN_DATADESC( CFailableAchievement ) +DEFINE_FIELD( m_bActivated, FIELD_BOOLEAN ), +DEFINE_FIELD( m_bFailed, FIELD_BOOLEAN ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: constructor +//----------------------------------------------------------------------------- +CBaseAchievement::CBaseAchievement() +{ + m_iFlags = 0; + m_iGoal = 0; + m_iProgressMsgIncrement = 0; + m_iProgressMsgMinimum = 0; + m_iAchievementID = 0; + m_iPointValue = 0; + m_bHideUntilAchieved = false; + m_bStoreProgressInSteam = false; + m_pVictimClassNameFilter = NULL; + m_pAttackerClassNameFilter = NULL; + m_pInflictorClassNameFilter = NULL; + m_pInflictorEntityNameFilter = NULL; + m_pMapNameFilter = NULL; + m_pGameDirFilter = NULL; + m_pszComponentNames = NULL; + m_pszComponentPrefix = NULL; + m_iNumComponents = 0; + m_iComponentPrefixLen = 0; + m_iComponentBits = 0; + m_iCount = 0; + m_iProgressShown = 0; + m_bAchieved = false; + m_uUnlockTime = 0; + m_pAchievementMgr = NULL; + m_bShowOnHUD = false; + m_pszStat = NULL; +} + +CBaseAchievement::~CBaseAchievement() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: sets flags +//----------------------------------------------------------------------------- +void CBaseAchievement::SetFlags( int iFlags ) +{ + // must always specify a save method + Assert( iFlags & ( ACH_SAVE_WITH_GAME | ACH_SAVE_GLOBAL ) ); + + m_iFlags = iFlags; +} + +//----------------------------------------------------------------------------- +// Purpose: called when a game event being listened for is dispatched +//----------------------------------------------------------------------------- +void CBaseAchievement::FireGameEvent( IGameEvent *event ) +{ + // perform common filtering to make it simpler to write achievements + if ( !IsActive() ) + return; + + // if the achievement only applies to a specific map, and it's not the current map, skip it + if ( m_pMapNameFilter && ( 0 != Q_strcmp( m_pAchievementMgr->GetMapName(), m_pMapNameFilter ) ) ) + return; + + const char *name = event->GetName(); + if ( 0 == Q_strcmp( name, "teamplay_round_win" ) ) + { + // if this is a round win and the achievement wants full round events only, filter this out + // if this is not the end of a full round + if ( ( m_iFlags & ACH_FILTER_FULL_ROUND_ONLY ) && ( false == event->GetBool( "full_round" ) ) ) + return; + } + + // let the achievement handle the event + FireGameEvent_Internal( event ); +} + + +//----------------------------------------------------------------------------- +// Purpose: sets victim class to filter with +//----------------------------------------------------------------------------- +void CBaseAchievement::SetVictimFilter( const char *pClassName ) +{ + m_pVictimClassNameFilter = pClassName; +} + +//----------------------------------------------------------------------------- +// Purpose: sets attacker class to filter with +//----------------------------------------------------------------------------- +void CBaseAchievement::SetAttackerFilter( const char *pClassName ) +{ + m_pAttackerClassNameFilter = pClassName; +} + +//----------------------------------------------------------------------------- +// Purpose: sets inflictor class to filter with +//----------------------------------------------------------------------------- +void CBaseAchievement::SetInflictorFilter( const char *pClassName ) +{ + m_pInflictorClassNameFilter = pClassName; +} + +//----------------------------------------------------------------------------- +// Purpose: sets inflictor entity name to filter with +//----------------------------------------------------------------------------- +void CBaseAchievement::SetInflictorEntityNameFilter( const char *pEntityName ) +{ + m_pInflictorEntityNameFilter = pEntityName; +} + +//----------------------------------------------------------------------------- +// Purpose: sets map name to filter with +//----------------------------------------------------------------------------- +void CBaseAchievement::SetMapNameFilter( const char *pMapName ) +{ + m_pMapNameFilter = pMapName; +} + +//----------------------------------------------------------------------------- +// Purpose: sets game dir to filter with. Note: in general, achievements should +// only be compiled into products they pertain to. But if there are +// any game-specific achievements which need to be in a binary shared +// across products (e.g. Ep1 & Ep2), use the game dir as a runtime +// filter. +//----------------------------------------------------------------------------- +void CBaseAchievement::SetGameDirFilter( const char *pGameDir ) +{ + m_pGameDirFilter = pGameDir; +} + +//----------------------------------------------------------------------------- +// Purpose: sets prefix to look for in map event string to identify a component +// for this achievement +//----------------------------------------------------------------------------- +void CBaseAchievement::SetComponentPrefix( const char *pPrefix ) +{ + m_pszComponentPrefix = pPrefix; + m_iComponentPrefixLen = Q_strlen( pPrefix ); +} + +//----------------------------------------------------------------------------- +// Purpose: called when a kill that passes filter critera occurs. This +// is the default implementation, achievements can override to +// do special handling +//----------------------------------------------------------------------------- +void CBaseAchievement::Event_EntityKilled( CBaseEntity *pVictim, CBaseEntity *pAttacker, CBaseEntity *pInflictor, IGameEvent *event ) +{ + // extra paranoid check: should only get here if registered as a kill event listener + Assert( GetFlags() & ACH_LISTEN_KILL_EVENTS ); + if ( !( GetFlags() & ACH_LISTEN_KILL_EVENTS ) ) + return; + + // default implementation is just to increase count when filter criteria pass + // Msg( "Base achievement incremented on kill event.\n" ); + IncrementCount(); +} + +//----------------------------------------------------------------------------- +// Purpose: called when an event that counts toward an achievement occurs +//----------------------------------------------------------------------------- +void CBaseAchievement::IncrementCount( int iOptIncrement ) +{ + if ( !IsAchieved() && LocalPlayerCanEarn() ) + { + if ( !AlwaysEnabled() && !m_pAchievementMgr->CheckAchievementsEnabled() ) + { + Msg( "Achievements disabled, ignoring achievement progress for %s\n", GetName() ); + return; + } + + // on client, where the count is kept, increment count + if ( iOptIncrement > 0 ) + { + // user specified that we want to increase by more than one. + m_iCount += iOptIncrement; + if ( m_iCount > m_iGoal ) + { + m_iCount = m_iGoal; + } + } + else + { + m_iCount++; + } + + // if this achievement gets saved w/global state, flag our global state as dirty + if ( GetFlags() & ACH_SAVE_GLOBAL ) + { + m_pAchievementMgr->SetDirty( true ); + } + + if ( cc_achievement_debug.GetInt() ) + { + Msg( "Achievement count increased for %s: %d/%d\n", GetName(), m_iCount, m_iGoal ); + } + +#ifndef NO_STEAM + // if this achievement's progress should be stored in Steam, set the steam stat for it + if ( StoreProgressInSteam() && steamapicontext->SteamUserStats() ) + { + // Set the Steam stat with the same name as the achievement. Only cached locally until we upload it. + char pszProgressName[1024]; + Q_snprintf( pszProgressName, 1024, "%s_STAT", GetStat() ); + bool bRet = steamapicontext->SteamUserStats()->SetStat( pszProgressName, m_iCount ); + if ( !bRet ) + { + DevMsg( "ISteamUserStats::GetStat failed to set progress value in Steam for achievement %s\n", pszProgressName ); + } + + m_pAchievementMgr->SetDirty( true ); + } +#endif + // if we've hit goal, award the achievement + if ( m_iGoal > 0 ) + { + if ( m_iCount >= m_iGoal ) + { + AwardAchievement(); + } + else + { + HandleProgressUpdate(); + } + } + + } +} + +void CBaseAchievement::SetShowOnHUD( bool bShow ) +{ + if ( m_bShowOnHUD != bShow ) + { + m_pAchievementMgr->SetDirty( true ); + } + + m_bShowOnHUD = bShow; +} + +void CBaseAchievement::HandleProgressUpdate() +{ + // if we've hit the right # of progress steps to show a progress notification, show it + if ( ( m_iProgressMsgIncrement > 0 ) && m_iCount >= m_iProgressMsgMinimum && ( 0 == ( m_iCount % m_iProgressMsgIncrement ) ) ) + { + // which notification is this + int iProgress = m_iCount / m_iProgressMsgIncrement; + // if we haven't already shown this progress step, show it + if ( iProgress > m_iProgressShown ) + { + ShowProgressNotification(); + // remember progress step shown so we don't show it again if the player loads an earlier save game + // and gets past this point again + m_iProgressShown = iProgress; + m_pAchievementMgr->SetDirty( true ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: calculates at how many steps we should show a progress notification +//----------------------------------------------------------------------------- +void CBaseAchievement::CalcProgressMsgIncrement() +{ + // by default, show progress at every 25% + m_iProgressMsgIncrement = m_iGoal / 4; + // if goal is not evenly divisible by 4, try some other values + if ( 0 != ( m_iGoal % 4 ) ) + { + if ( 0 == ( m_iGoal % 3 ) ) + { + // if evenly divisible by 3, use that + m_iProgressMsgIncrement = m_iGoal / 3; + } + else if ( 0 == ( m_iGoal % 5 ) ) + { + // if evenly divisible by 5, use that + m_iProgressMsgIncrement = m_iGoal / 5; + } + // otherwise stick with divided by 4, rounded off + } + + // don't show progress notifications for less than 5 things + if ( m_iProgressMsgIncrement < 5 ) + { + m_iProgressMsgIncrement = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseAchievement::SetNextThink( float flThinkTime ) +{ + m_pAchievementMgr->SetAchievementThink( this, flThinkTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseAchievement::ClearThink( void ) +{ + m_pAchievementMgr->SetAchievementThink( this, THINK_CLEAR ); +} + +//----------------------------------------------------------------------------- +// Purpose: see if we should award an achievement based on what just happened +//----------------------------------------------------------------------------- +void CBaseAchievement::EvaluateNewAchievement() +{ + if ( !IsAchieved() && m_iGoal > 0 && m_iCount >= m_iGoal ) + { + AwardAchievement(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: determine if we should set this achievement to be achieved based +// on other state. Used at init time. +//----------------------------------------------------------------------------- +void CBaseAchievement::EvaluateIsAlreadyAchieved() +{ + if ( !IsAchieved() && m_iGoal > 0 && m_iCount >= m_iGoal ) + { + m_bAchieved = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: called a map event for this achievement occurs +//----------------------------------------------------------------------------- +void CBaseAchievement::OnMapEvent( const char *pEventName ) +{ + Assert( m_iFlags & ACH_LISTEN_MAP_EVENTS ); + + if ( 0 == Q_stricmp( pEventName, GetName() ) ) + { + IncrementCount(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when an achievement is awarded +//----------------------------------------------------------------------------- +void CBaseAchievement::AwardAchievement() +{ + Assert( !IsAchieved() ); + if ( IsAchieved() ) + return; + + m_pAchievementMgr->AwardAchievement( m_iAchievementID ); +} + +//----------------------------------------------------------------------------- +// Purpose: called when a component of a multi-component event is found +//----------------------------------------------------------------------------- +void CBaseAchievement::OnComponentEvent( const char *pchComponentName ) +{ + // find the component name in our list + for ( int i = 0; i < m_iNumComponents; i++ ) + { + if ( 0 == Q_strcmp( pchComponentName, m_pszComponentNames[i] ) ) + { + EnsureComponentBitSetAndEvaluate( i ); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: sets the specified component bit # if it is not already. +// If it does get set, evaluate if this satisfies an achievement +//----------------------------------------------------------------------------- +void CBaseAchievement::EnsureComponentBitSetAndEvaluate( int iBitNumber ) +{ + Assert( iBitNumber < 64 ); // this is bit #, not a bit mask + + if ( IsAchieved() ) + return; + + // calculate which bit this component corresponds to + uint64 iBitMask = ( (uint64) 1 ) << iBitNumber; + + // see if we already have gotten this component + if ( 0 == ( iBitMask & m_iComponentBits ) ) + { + if ( !AlwaysEnabled() && !m_pAchievementMgr->CheckAchievementsEnabled() ) + { + Msg( "Achievements disabled, ignoring achievement component for %s\n", GetName() ); + return; + } + + // new component, set the bit and increment the count + SetComponentBits( m_iComponentBits | iBitMask ); + Assert( m_iCount <= m_iGoal ); + if ( m_iCount == m_iGoal ) + { + // all components found, award the achievement (and save state) + AwardAchievement(); + } + else + { + // save our state at the next good opportunity + m_pAchievementMgr->SetDirty( true ); + + if ( cc_achievement_debug.GetInt() ) + { + Msg( "Component %d for achievement %s found\n", iBitNumber, GetName() ); + } + + ShowProgressNotification(); + } + } + else + { + if ( cc_achievement_debug.GetInt() ) + { + Msg( "Component %d for achievement %s found, but already had that component\n", iBitNumber, GetName() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: displays achievement progress notification in the HUD +//----------------------------------------------------------------------------- +void CBaseAchievement::ShowProgressNotification() +{ + if ( !ShouldShowProgressNotification() ) + return; + + IGameEvent *event = gameeventmanager->CreateEvent( "achievement_event" ); + if ( event ) + { + event->SetString( "achievement_name", GetName() ); + event->SetInt( "cur_val", m_iCount ); + event->SetInt( "max_val", m_iGoal ); +#ifdef GAME_DLL + gameeventmanager->FireEvent( event ); +#else + gameeventmanager->FireEventClientSide( event ); +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: clears dynamic state for this achievement +//----------------------------------------------------------------------------- +void CBaseAchievement::PreRestoreSavedGame() +{ + // if this achievement gets saved with the game, clear its state + if ( m_iFlags & ACH_SAVE_WITH_GAME ) + { + m_iCount = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: called after the data in this achievement has been restored from saved game +//----------------------------------------------------------------------------- +void CBaseAchievement::PostRestoreSavedGame() +{ + EvaluateIsAlreadyAchieved(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets component bits for this achievement +//----------------------------------------------------------------------------- +void CBaseAchievement::SetComponentBits( uint64 iComponentBits ) +{ + Assert( m_iFlags & ACH_HAS_COMPONENTS ); + // set the bit field + m_iComponentBits = iComponentBits; + // count how many bits are set and save that as the count + int iNumBitsSet = 0; + while ( iComponentBits > 0 ) + { + if ( iComponentBits & 1 ) + { + iNumBitsSet++; + } + iComponentBits >>= 1; + } + m_iCount = iNumBitsSet; +} + +//----------------------------------------------------------------------------- +// Purpose: returns whether we should save this achievement with a save game +//----------------------------------------------------------------------------- +bool CBaseAchievement::ShouldSaveWithGame() +{ + // save if we should get saved with the game, have a non-zero count, and have not + // been achieved (at which point the achievement state gets saved globally) + return ( ( m_iFlags & ACH_SAVE_WITH_GAME ) > 0 && ( GetCount() > 0 ) && !IsAchieved() ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns whether we should save this achievement to the global file +//----------------------------------------------------------------------------- +bool CBaseAchievement::ShouldSaveGlobal() +{ + // save if we should get saved globally and have a non-zero count, or if we have been achieved, or if the player has pinned this achievement to the HUD + return ( ( ( m_iFlags & ACH_SAVE_GLOBAL ) > 0 && ( GetCount() > 0 ) ) || IsAchieved() || ( m_iProgressShown > 0 ) || ShouldShowOnHUD() ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns whether this achievement is active +//----------------------------------------------------------------------------- +bool CBaseAchievement::IsActive() +{ + // we're not active if already achieved + if ( IsAchieved() ) + return false; + + // if there's a map filter and we're not on the specified map, we're not active + if ( ( m_pMapNameFilter ) && ( 0 != Q_strcmp( m_pAchievementMgr->GetMapName(), m_pMapNameFilter ) ) ) + return false; + + return true; +} + +//============================================================================= +// HPE_BEGIN: +// [pfreese] Moved serialization from AchievementMgr to here so that derived +// classes can persist additional data +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: Serialize our data to the KeyValues node +//----------------------------------------------------------------------------- +void CBaseAchievement::GetSettings( KeyValues *pNodeOut ) +{ + pNodeOut->SetInt( "value", IsAchieved() ? 1 : 0 ); + + if ( HasComponents() ) + { + pNodeOut->SetUint64( "data", m_iComponentBits ); + } + else + { + if ( !IsAchieved() ) + { + pNodeOut->SetInt( "data", m_iCount ); + } + } + pNodeOut->SetInt( "hud", ShouldShowOnHUD() ? 1 : 0 ); + pNodeOut->SetInt( "msg", m_iProgressShown ); +} + +//----------------------------------------------------------------------------- +// Purpose: Unserialize our data from the KeyValues node +//----------------------------------------------------------------------------- +void CBaseAchievement::ApplySettings( KeyValues *pNodeIn ) +{ + // set the count + if ( pNodeIn->GetInt( "value" ) > 0 ) + { + m_iCount = m_iGoal; + m_bAchieved = true; + } + else if ( !HasComponents() ) + { + m_iCount = pNodeIn->GetInt( "data" ); + } + + // if this achievement has components, set the component bits + if ( HasComponents() ) + { + int64 iComponentBits = pNodeIn->GetUint64( "data" ); + SetComponentBits( iComponentBits ); + } + SetShowOnHUD( !!pNodeIn->GetInt( "hud" ) ); + m_iProgressShown = pNodeIn->GetInt( "msg" ); +} + +//============================================================================= +// HPE_END +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CFailableAchievement::CFailableAchievement() : CBaseAchievement() +{ + m_bFailed = false; + m_bActivated = false; +} + +//----------------------------------------------------------------------------- +// Purpose: returns whether we should save this achievement with a save game +//----------------------------------------------------------------------------- +bool CFailableAchievement::ShouldSaveWithGame() +{ + // save if we should get saved with the game, and are active or have failed + return ( ( ( m_iFlags & ACH_SAVE_WITH_GAME ) > 0 ) && ( m_bActivated || m_bFailed ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: clears dynamic state for this achievement +//----------------------------------------------------------------------------- +void CFailableAchievement::PreRestoreSavedGame() +{ + m_bFailed = false; + m_bActivated = false; + + BaseClass::PreRestoreSavedGame(); +} + + +//----------------------------------------------------------------------------- +// Purpose: called after the data in this achievement has been restored from saved game +//----------------------------------------------------------------------------- +void CFailableAchievement::PostRestoreSavedGame() +{ + // if there is no activation event set for this achievement, it is always active, activate it now + if ( !m_bFailed && !GetActivationEventName()[0] ) + { + m_bActivated = true; + } + + if ( m_bActivated ) + { + Activate(); + } + + BaseClass::PostRestoreSavedGame(); +} + +//----------------------------------------------------------------------------- +// Purpose: called when a map event occurs +//----------------------------------------------------------------------------- +void CFailableAchievement::OnMapEvent( const char *pEventName ) +{ + // if we're not activated and we got the activation event, activate + if ( !m_bActivated && ( 0 == Q_stricmp( pEventName, GetActivationEventName() ) ) ) + { + OnActivationEvent(); + } + // if this is the evaluation event, see if we've failed or not + else if ( m_bActivated && 0 == Q_stricmp( pEventName, GetEvaluationEventName() ) ) + { + OnEvaluationEvent(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when this failable achievement is activated +//----------------------------------------------------------------------------- +void CFailableAchievement::Activate() +{ + m_bActivated = true; + ListenForEvents(); + if ( cc_achievement_debug.GetInt() ) + { + Msg( "Failable achievement %s now active\n", GetName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called when this failable achievement should be evaluated +//----------------------------------------------------------------------------- +void CFailableAchievement::OnEvaluationEvent() +{ + if ( !m_bFailed ) + { + // we haven't failed and we reached the evaluation point, we've succeeded + IncrementCount(); + } + + if ( cc_achievement_debug.GetInt() ) + { + Msg( "Failable achievement %s has been evaluated (%s), now inactive\n", GetName(), m_bFailed ? "FAILED" : "AWARDED" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets this achievement to failed +//----------------------------------------------------------------------------- +void CFailableAchievement::SetFailed() +{ + if ( !m_bFailed ) + { + m_bFailed = true; + + if ( cc_achievement_debug.GetInt() ) + { + Msg( "Achievement failed: %s (%s)\n", GetName(), GetName() ); + } + } +} + +//=========================================== + +void CAchievement_AchievedCount::Init() +{ + SetFlags( ACH_SAVE_GLOBAL ); + SetGoal( 1 ); + SetAchievementsRequired( 0, 0, 0 ); +} + +// Count how many achievements have been earned in our range +void CAchievement_AchievedCount::OnSteamUserStatsStored( void ) +{ + // DO NO CALL. REPLACED BY CHECKMETAACHIEVEMENTS! + Assert( 0 ); +} + +void CAchievement_AchievedCount::SetAchievementsRequired( int iNumRequired, int iLowRange, int iHighRange ) +{ + m_iNumRequired = iNumRequired; + m_iLowRange = iLowRange; + m_iHighRange = iHighRange; }
\ No newline at end of file |