From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/shared/baseachievement.cpp | 1494 ++++++++++++++++---------------- 1 file changed, 747 insertions(+), 747 deletions(-) (limited to 'mp/src/game/shared/baseachievement.cpp') 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 -#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 +#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 -- cgit v1.2.3