forked from hitmen047/Source-PlusPlus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathachievementmgr.cpp
2074 lines (1823 loc) · 62.8 KB
/
achievementmgr.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#ifdef _WIN32
#include "winerror.h"
#endif
#include "achievementmgr.h"
#include "icommandline.h"
#include "KeyValues.h"
#include "filesystem.h"
#include "inputsystem/InputEnums.h"
#include "usermessages.h"
#include "fmtstr.h"
#include "tier1/utlbuffer.h"
#ifdef CLIENT_DLL
#include "achievement_notification_panel.h"
#include "c_playerresource.h"
#include "gamestats.h"
#ifdef TF_CLIENT_DLL
#include "econ_item_inventory.h"
#endif //TF_CLIENT_DLL
#else
#include "enginecallback.h"
#endif // CLIENT_DLL
#ifndef _X360
#include "steam/isteamuserstats.h"
#include "steam/isteamfriends.h"
#include "steam/isteamutils.h"
#include "steam/steam_api.h"
#include "steam/isteamremotestorage.h"
#else
#include "xbox/xbox_win32stubs.h"
#endif
#include "tier3/tier3.h"
#include "vgui/ILocalize.h"
#ifdef _X360
#include "ixboxsystem.h"
#endif // _X360
#include "engine/imatchmaking.h"
#include "tier0/vprof.h"
#if defined(TF_DLL) || defined(TF_CLIENT_DLL)
#include "tf_gamerules.h"
#endif
ConVar cc_achievement_debug( "achievement_debug", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Turn on achievement debug msgs." );
#ifdef CSTRIKE_DLL
//=============================================================================
// HPE_BEGIN:
// [Forrest] Allow achievements/stats to be turned off for a server
//=============================================================================
ConVar sv_nostats( "sv_nostats", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Disable collecting statistics and awarding achievements." );
//=============================================================================
// HPE_END
//=============================================================================
#endif // CSTRIKE_DLL
const char *COM_GetModDirectory();
extern ConVar developer;
#define DEBUG_ACHIEVEMENTS_IN_RELEASE 0
#ifdef SWDS
// Hack this for now until we get steam_api recompiling in the Steam codebase.
ISteamUserStats *SteamUserStats()
{
return NULL;
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Write helper
//-----------------------------------------------------------------------------
//=============================================================================
// HPE_BEGIN
// [dwenger] Steam Cloud Support
//=============================================================================
static void WriteAchievementGlobalState( KeyValues *pKV, bool bPersistToSteamCloud = false )
//=============================================================================
// HPE_END
//=============================================================================
{
#ifdef _X360
if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
return;
#endif
char szFilename[_MAX_PATH];
if ( IsX360() )
{
Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/%s_GameState.txt", COM_GetModDirectory() );
}
else
{
Q_snprintf( szFilename, sizeof( szFilename ), "GameState.txt" );
}
// Never call pKV->SaveToFile!!!!
// Save to a buffer instead.
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
pKV->RecursiveSaveToFile( buf, 0 );
filesystem->WriteFile( szFilename, NULL, buf );
pKV->deleteThis();
//=============================================================================
// HPE_BEGIN
// [dwenger] Steam Cloud Support
//=============================================================================
if ( bPersistToSteamCloud )
{
#ifndef NO_STEAM
if ( IsX360() )
{
Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/%s_GameState.txt", COM_GetModDirectory() );
}
else
{
Q_snprintf( szFilename, sizeof( szFilename ), "GameState.txt" );
}
ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
if (pRemoteStorage)
{
int32 availableBytes = 0;
int32 totalBytes = 0;
if ( pRemoteStorage->GetQuota( &totalBytes, &availableBytes ) )
{
if ( totalBytes > 0 )
{
int32 filesize = (int32)filesystem->Size(szFilename);
if (filesize > 0)
{
char* pData = new char[filesize];
if (pData)
{
// Read in the data from the file system GameState.txt file
FileHandle_t handle = filesystem->Open(szFilename, "r");
if (handle)
{
int32 nRead = filesystem->Read(pData, filesize, handle);
filesystem->Close(handle);
if (nRead == filesize)
{
// Write out the data to steam cloud
pRemoteStorage->FileWrite(szFilename, pData, filesize);
}
}
// Delete the data array
delete []pData;
}
}
}
}
}
#endif
}
//=============================================================================
// HPE_END
//=============================================================================
#ifdef _X360
if ( xboxsystem )
{
xboxsystem->FinishContainerWrites();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Async save thread
//-----------------------------------------------------------------------------
class CAchievementSaveThread : public CWorkerThread
{
public:
CAchievementSaveThread() :
m_pKV( NULL )
{
SetName( "AchievementSaveThread" );
}
~CAchievementSaveThread()
{
}
enum
{
CALL_FUNC,
EXIT,
};
void WriteAchievementGlobalState( KeyValues *pKV )
{
Assert( !m_pKV );
m_pKV = pKV;
CallWorker( CALL_FUNC );
Assert( !m_pKV );
}
int Run()
{
unsigned nCall;
while ( WaitForCall( &nCall ) )
{
if ( nCall == EXIT )
{
Reply( 1 );
break;
}
KeyValues *pKV = m_pKV;
m_pKV = NULL;
Reply( 1 );
::WriteAchievementGlobalState( pKV );
}
return 0;
}
private:
KeyValues *m_pKV;
};
static CAchievementSaveThread g_AchievementSaveThread;
//-----------------------------------------------------------------------------
// Purpose: constructor
//-----------------------------------------------------------------------------
//=============================================================================
// HPE_BEGIN
// [dwenger] Steam Cloud Support
//=============================================================================
CAchievementMgr::CAchievementMgr( SteamCloudPersisting ePersistToSteamCloud ) : CAutoGameSystemPerFrame( "CAchievementMgr" )
//=============================================================================
// HPE_END
//=============================================================================
#if !defined(NO_STEAM)
, m_CallbackUserStatsReceived( this, &CAchievementMgr::Steam_OnUserStatsReceived ),
m_CallbackUserStatsStored( this, &CAchievementMgr::Steam_OnUserStatsStored )
#endif
{
SetDefLessFunc( m_mapAchievement );
SetDefLessFunc( m_mapMetaAchievement );
m_flLastClassChangeTime = 0;
m_flTeamplayStartTime = 0;
m_iMiniroundsCompleted = 0;
m_szMap[0] = 0;
m_bSteamDataDirty = false;
m_bGlobalStateDirty = false;
m_bGlobalStateLoaded = false;
m_bCheatsEverOn = false;
m_flTimeLastSaved = 0;
//=============================================================================
// HPE_BEGIN
// [dwenger] Steam Cloud Support
//=============================================================================
if ( ePersistToSteamCloud == SteamCloudPersist_Off )
{
m_bPersistToSteamCloud = false;
}
else
{
m_bPersistToSteamCloud = true;
}
//=============================================================================
// HPE_END
//=============================================================================
m_AchievementsAwarded.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: Initializer
//-----------------------------------------------------------------------------
bool CAchievementMgr::Init()
{
// We can be created on either client (for multiplayer games) or server
// (for single player), so register ourselves with the engine so UI has a uniform place
// to go get the pointer to us
#ifdef _DEBUG
// There can be only one achievement manager instance; no one else should be registered
IAchievementMgr *pAchievementMgr = engine->GetAchievementMgr();
Assert( NULL == pAchievementMgr );
#endif // _DEBUG
// register ourselves
engine->SetAchievementMgr( this );
// register for events
#ifdef GAME_DLL
ListenForGameEvent( "entity_killed" );
ListenForGameEvent( "game_init" );
#else
ListenForGameEvent( "player_death" );
ListenForGameEvent( "player_stats_updated" );
usermessages->HookMessage( "AchievementEvent", MsgFunc_AchievementEvent );
#endif // CLIENT_DLL
#ifdef TF_CLIENT_DLL
ListenForGameEvent( "localplayer_changeclass" );
ListenForGameEvent( "localplayer_changeteam" );
ListenForGameEvent( "teamplay_round_start" );
ListenForGameEvent( "teamplay_round_win" );
#endif // TF_CLIENT_DLL
return true;
}
//-----------------------------------------------------------------------------
// Purpose: called at init time after all systems are init'd. We have to
// do this in PostInit because the Steam app ID is not available earlier
//-----------------------------------------------------------------------------
void CAchievementMgr::PostInit()
{
if ( !g_AchievementSaveThread.IsAlive() )
{
g_AchievementSaveThread.Start();
#ifdef WIN32
if ( IsX360() )
{
ThreadSetAffinity( (ThreadHandle_t)g_AchievementSaveThread.GetThreadHandle(), XBOX_PROCESSOR_3 );
}
#endif // WIN32
}
// get current game dir
const char *pGameDir = COM_GetModDirectory();
CBaseAchievementHelper *pAchievementHelper = CBaseAchievementHelper::s_pFirst;
while ( pAchievementHelper )
{
// create and initialize all achievements and insert them in our map
CBaseAchievement *pAchievement = pAchievementHelper->m_pfnCreate();
pAchievement->m_pAchievementMgr = this;
pAchievement->Init();
pAchievement->CalcProgressMsgIncrement();
// only add an achievement if it does not have a game filter (only compiled into the game it
// applies to, or truly cross-game) or, if it does have a game filter, the filter matches current game.
// (e.g. EP 1/2/... achievements are in shared binary but are game specific, they have a game filter for runtime check.)
const char *pGameDirFilter = pAchievement->m_pGameDirFilter;
if ( !pGameDirFilter || ( 0 == Q_strcmp( pGameDir, pGameDirFilter ) ) )
{
m_mapAchievement.Insert( pAchievement->GetAchievementID(), pAchievement );
if ( pAchievement->IsMetaAchievement() )
{
m_mapMetaAchievement.Insert( pAchievement->GetAchievementID(), dynamic_cast<CAchievement_AchievedCount*>(pAchievement) );
}
}
else
{
// achievement is not for this game, don't use it
delete pAchievement;
}
pAchievementHelper = pAchievementHelper->m_pNext;
}
FOR_EACH_MAP( m_mapAchievement, iter )
{
m_vecAchievement.AddToTail( m_mapAchievement[iter] );
}
// load global state from file
LoadGlobalState();
// download achievements/stats from Steam/XBox Live
DownloadUserData();
}
//-----------------------------------------------------------------------------
// Purpose: Shuts down the achievement manager
//-----------------------------------------------------------------------------
void CAchievementMgr::Shutdown()
{
g_AchievementSaveThread.CallWorker( CAchievementSaveThread::EXIT );
SaveGlobalState( false ); // we just told the thread to shutdown so don't try an async save here
FOR_EACH_MAP( m_mapAchievement, iter )
{
delete m_mapAchievement[iter];
}
m_mapAchievement.RemoveAll();
m_mapMetaAchievement.RemoveAll();
m_vecAchievement.RemoveAll();
m_vecKillEventListeners.RemoveAll();
m_vecMapEventListeners.RemoveAll();
m_vecComponentListeners.RemoveAll();
m_AchievementsAwarded.RemoveAll();
m_bGlobalStateLoaded = false;
}
//-----------------------------------------------------------------------------
// Purpose: Cleans up all achievements and then re-initializes them
//-----------------------------------------------------------------------------
void CAchievementMgr::InitializeAchievements()
{
Shutdown();
PostInit();
}
#ifdef CLIENT_DLL
extern const ConVar *sv_cheats;
#endif
#ifdef GAME_DLL
void CAchievementMgr::FrameUpdatePostEntityThink()
{
Update( 0.0f );
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Do per-frame handling
//-----------------------------------------------------------------------------
void CAchievementMgr::Update( float frametime )
{
#ifdef CLIENT_DLL
if ( !sv_cheats )
{
sv_cheats = cvar->FindVar( "sv_cheats" );
}
#endif
#ifndef _DEBUG
// keep track if cheats have ever been turned on during this level
if ( !WereCheatsEverOn() )
{
if ( sv_cheats && sv_cheats->GetBool() )
{
m_bCheatsEverOn = true;
}
}
#endif
// Call think functions. Work backwards, because we may remove achievements from the list.
int iCount = m_vecThinkListeners.Count();
for ( int i = iCount-1; i >= 0; i-- )
{
if ( m_vecThinkListeners[i].m_flThinkTime < gpGlobals->curtime )
{
m_vecThinkListeners[i].pAchievement->Think();
// The think function may have pushed out the think time. If not, remove ourselves from the list.
if ( m_vecThinkListeners[i].pAchievement->IsAchieved() || m_vecThinkListeners[i].m_flThinkTime < gpGlobals->curtime )
{
m_vecThinkListeners.Remove(i);
}
}
}
if ( m_bSteamDataDirty )
{
UploadUserData();
}
}
//-----------------------------------------------------------------------------
// Purpose: called on level init
//-----------------------------------------------------------------------------
void CAchievementMgr::LevelInitPreEntity()
{
m_bCheatsEverOn = false;
// load global state if we haven't already; X360 users may not have had a storage device available or selected at boot time
EnsureGlobalStateLoaded();
#ifdef GAME_DLL
// For single-player games, achievement mgr must live on the server. (Only the server has detailed knowledge of game state.)
Assert( !GameRules()->IsMultiplayer() );
#else
// For multiplayer games, achievement mgr must live on the client. (Only the client can read/write player state from Steam/XBox Live.)
Assert( GameRules()->IsMultiplayer() );
#endif
// clear list of achievements listening for events
m_vecKillEventListeners.RemoveAll();
m_vecMapEventListeners.RemoveAll();
m_vecComponentListeners.RemoveAll();
m_AchievementsAwarded.RemoveAll();
m_flLastClassChangeTime = 0;
m_flTeamplayStartTime = 0;
m_iMiniroundsCompleted = 0;
// client and server have map names available in different forms (full path on client, just file base name on server),
// cache it in base file name form here so we don't have to have different code paths each time we access it
#ifdef CLIENT_DLL
Q_FileBase( engine->GetLevelName(), m_szMap, ARRAYSIZE( m_szMap ) );
#else
Q_strncpy( m_szMap, gpGlobals->mapname.ToCStr(), ARRAYSIZE( m_szMap ) );
#endif // CLIENT_DLL
if ( IsX360() )
{
// need to remove the .360 extension on the end of the map name
char *pExt = Q_stristr( m_szMap, ".360" );
if ( pExt )
{
*pExt = '\0';
}
}
// look through all achievements, see which ones we want to have listen for events
FOR_EACH_MAP( m_mapAchievement, iAchievement )
{
CBaseAchievement *pAchievement = m_mapAchievement[iAchievement];
// if the achievement only applies to a specific map, and it's not the current map, skip it
const char *pMapNameFilter = pAchievement->m_pMapNameFilter;
if ( pMapNameFilter && ( 0 != Q_strcmp( m_szMap, pMapNameFilter ) ) )
continue;
// if the achievement needs kill events, add it as a listener
if ( pAchievement->GetFlags() & ACH_LISTEN_KILL_EVENTS )
{
m_vecKillEventListeners.AddToTail( pAchievement );
}
// if the achievement needs map events, add it as a listener
if ( pAchievement->GetFlags() & ACH_LISTEN_MAP_EVENTS )
{
m_vecMapEventListeners.AddToTail( pAchievement );
}
// if the achievement needs map events, add it as a listener
if ( pAchievement->GetFlags() & ACH_LISTEN_COMPONENT_EVENTS )
{
m_vecComponentListeners.AddToTail( pAchievement );
}
if ( pAchievement->IsActive() )
{
pAchievement->ListenForEvents();
}
}
m_flLevelInitTime = gpGlobals->curtime;
}
//-----------------------------------------------------------------------------
// Purpose: called on level shutdown
//-----------------------------------------------------------------------------
void CAchievementMgr::LevelShutdownPreEntity()
{
// make all achievements stop listening for events
FOR_EACH_MAP( m_mapAchievement, iAchievement )
{
CBaseAchievement *pAchievement = m_mapAchievement[iAchievement];
if ( !pAchievement->AlwaysListen() )
{
pAchievement->StopListeningForAllEvents();
}
}
// save global state if we have any changes
SaveGlobalStateIfDirty();
UploadUserData();
}
//-----------------------------------------------------------------------------
// Purpose: returns achievement for specified ID
//-----------------------------------------------------------------------------
CBaseAchievement *CAchievementMgr::GetAchievementByID( int iAchievementID )
{
int iAchievement = m_mapAchievement.Find( iAchievementID );
if ( iAchievement != m_mapAchievement.InvalidIndex() )
{
return m_mapAchievement[iAchievement];
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: returns achievement with specified name. NOTE: this iterates through
// all achievements to find the name, intended for debugging purposes.
// Use GetAchievementByID for fast lookup.
//-----------------------------------------------------------------------------
CBaseAchievement *CAchievementMgr::GetAchievementByName( const char *pchName )
{
VPROF("GetAchievementByName");
FOR_EACH_MAP_FAST( m_mapAchievement, i )
{
CBaseAchievement *pAchievement = m_mapAchievement[i];
if ( pAchievement && 0 == ( Q_stricmp( pchName, pAchievement->GetName() ) ) )
return pAchievement;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if the achievement with the specified name has been achieved
//-----------------------------------------------------------------------------
bool CAchievementMgr::HasAchieved( const char *pchName )
{
CBaseAchievement *pAchievement = GetAchievementByName( pchName );
if ( pAchievement )
return pAchievement->IsAchieved();
return false;
}
//-----------------------------------------------------------------------------
// Purpose: downloads user data from Steam or XBox Live
//-----------------------------------------------------------------------------
void CAchievementMgr::DownloadUserData()
{
if ( IsPC() )
{
#ifndef NO_STEAM
if ( steamapicontext->SteamUserStats() )
{
// request stat download; will get called back at OnUserStatsReceived when complete
steamapicontext->SteamUserStats()->RequestCurrentStats();
}
#endif
}
else if ( IsX360() )
{
#if defined( _X360 )
if ( XBX_GetPrimaryUserId() == INVALID_USER_ID )
return;
// Download achievements from XBox Live
bool bDownloadSuccessful = true;
int nTotalAchievements = 99;
uint bytes;
int ret = xboxsystem->EnumerateAchievements( XBX_GetPrimaryUserId(), 0, 0, nTotalAchievements, &bytes, 0, false );
if ( ret != ERROR_SUCCESS )
{
Warning( "Enumerate Achievements failed! Error %d", ret );
bDownloadSuccessful = false;
}
// Enumerate the achievements from Live
void *pBuffer = new byte[bytes];
if ( bDownloadSuccessful )
{
ret = xboxsystem->EnumerateAchievements( XBX_GetPrimaryUserId(), 0, 0, nTotalAchievements, pBuffer, bytes, false );
if ( ret != nTotalAchievements )
{
Warning( "Enumerate Achievements failed! Error %d", ret );
bDownloadSuccessful = false;
}
}
if ( bDownloadSuccessful )
{
// Give live a chance to mark achievements as unlocked, in case the achievement manager
// wasn't able to get that data (storage device missing, read failure, etc)
XACHIEVEMENT_DETAILS *pXboxAchievements = (XACHIEVEMENT_DETAILS*)pBuffer;
for ( int i = 0; i < nTotalAchievements; ++i )
{
CBaseAchievement *pAchievement = GetAchievementByID( pXboxAchievements[i].dwId );
if ( !pAchievement )
continue;
// Give Live a chance to claim the achievement as unlocked
if ( AchievementEarned( pXboxAchievements[i].dwFlags ) )
{
pAchievement->SetAchieved( true );
}
}
}
delete pBuffer;
#endif // X360
}
}
const char *COM_GetModDirectory()
{
static char modDir[MAX_PATH];
if ( Q_strlen( modDir ) == 0 )
{
const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) );
Q_strncpy( modDir, gamedir, sizeof(modDir) );
if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) )
{
Q_StripLastDir( modDir, sizeof(modDir) );
int dirlen = Q_strlen( modDir );
Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen );
}
}
return modDir;
}
//-----------------------------------------------------------------------------
// Purpose: uploads user data to steam
//-----------------------------------------------------------------------------
void CAchievementMgr::UploadUserData()
{
if ( IsPC() )
{
#ifndef NO_STEAM
if ( steamapicontext->SteamUserStats() )
{
// Upload current Steam client achievements & stats state to Steam. Will get called back at OnUserStatsStored when complete.
// Only values previously set via SteamUserStats() get uploaded
steamapicontext->SteamUserStats()->StoreStats();
m_bSteamDataDirty = false;
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose: loads global state from file
//-----------------------------------------------------------------------------
void CAchievementMgr::LoadGlobalState()
{
if ( IsX360() )
{
#ifdef _X360
if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
return;
#endif
}
char szFilename[_MAX_PATH];
if ( IsX360() )
{
Q_snprintf( szFilename, sizeof( szFilename ), "cfg:/%s_GameState.txt", COM_GetModDirectory() );
}
else
{
Q_snprintf( szFilename, sizeof( szFilename ), "GameState.txt" );
}
//=============================================================================
// HPE_BEGIN
// [dwenger] Steam Cloud Support
//=============================================================================
if ( m_bPersistToSteamCloud )
{
#ifndef NO_STEAM
ISteamRemoteStorage *pRemoteStorage = SteamClient()?(ISteamRemoteStorage *)SteamClient()->GetISteamGenericInterface(
SteamAPI_GetHSteamUser(), SteamAPI_GetHSteamPipe(), STEAMREMOTESTORAGE_INTERFACE_VERSION ):NULL;
if (pRemoteStorage)
{
if (pRemoteStorage->FileExists(szFilename))
{
int32 fileSize = pRemoteStorage->GetFileSize(szFilename);
if (fileSize > 0)
{
// Allocate space for the file data
char* pData = new char[fileSize];
if (pData)
{
int32 sizeRead = pRemoteStorage->FileRead(szFilename, pData, fileSize);
if (sizeRead == fileSize)
{
// Write out data to a filesystem GameState file that can be read by the original code below
FileHandle_t handle = filesystem->Open(szFilename, "w");
if (handle)
{
filesystem->Write(pData, fileSize, handle);
filesystem->Close(handle);
}
}
// Delete the data array
delete []pData;
}
}
}
}
#endif
}
//=============================================================================
// HPE_END
//=============================================================================
KeyValues *pKV = new KeyValues("GameState" );
if ( pKV->LoadFromFile( filesystem, szFilename, "MOD" ) )
{
KeyValues *pNode = pKV->GetFirstSubKey();
while ( pNode )
{
// look up this achievement
int iAchievementID = pNode->GetInt( "id", 0 );
if ( iAchievementID > 0 )
{
CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID );
if ( pAchievement )
{
pAchievement->ApplySettings(pNode);
}
}
pNode = pNode->GetNextKey();
}
m_bGlobalStateLoaded = true;
}
}
//-----------------------------------------------------------------------------
// Purpose: saves global state to file
//-----------------------------------------------------------------------------
void CAchievementMgr::SaveGlobalState( bool bAsync )
{
VPROF_BUDGET( "CAchievementMgr::SaveGlobalState", "Achievements" );
KeyValues *pKV = new KeyValues("GameState" );
FOR_EACH_MAP( m_mapAchievement, i )
{
CBaseAchievement *pAchievement = m_mapAchievement[i];
if ( pAchievement->ShouldSaveGlobal() )
{
KeyValues *pNode = pKV->CreateNewKey();
pNode->SetInt( "id", pAchievement->GetAchievementID() );
pAchievement->GetSettings(pNode);
}
}
if ( !bAsync )
{
WriteAchievementGlobalState( pKV, m_bPersistToSteamCloud );
}
else
{
g_AchievementSaveThread.WriteAchievementGlobalState( pKV );
}
m_flTimeLastSaved = Plat_FloatTime();
m_bGlobalStateDirty = false;
}
//-----------------------------------------------------------------------------
// Purpose: loads global state if we have not already successfully loaded it
//-----------------------------------------------------------------------------
void CAchievementMgr::EnsureGlobalStateLoaded()
{
if ( !m_bGlobalStateLoaded )
{
LoadGlobalState();
}
}
//-----------------------------------------------------------------------------
// Purpose: saves global state to file if there have been any changes
//-----------------------------------------------------------------------------
void CAchievementMgr::SaveGlobalStateIfDirty( bool bAsync )
{
if ( m_bGlobalStateDirty )
{
SaveGlobalState( bAsync );
}
}
//-----------------------------------------------------------------------------
// Purpose: awards specified achievement
//-----------------------------------------------------------------------------
void CAchievementMgr::AwardAchievement( int iAchievementID )
{
CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID );
Assert( pAchievement );
if ( !pAchievement )
return;
if ( !pAchievement->AlwaysEnabled() && !CheckAchievementsEnabled() )
{
Msg( "Achievements disabled, ignoring achievement unlock for %s\n", pAchievement->GetName() );
return;
}
if ( pAchievement->IsAchieved() )
{
if ( cc_achievement_debug.GetInt() > 0 )
{
Msg( "Achievement award called but already achieved: %s\n", pAchievement->GetName() );
}
return;
}
pAchievement->SetAchieved( true );
#ifdef CLIENT_DLL
if ( gamestats )
{
gamestats->Event_AchievementProgress( pAchievement->GetAchievementID(), pAchievement->GetName() );
}
#endif
//=============================================================================
// HPE_BEGIN
//=============================================================================
// [dwenger] Necessary for sorting achievements by award time
pAchievement->OnAchieved();
// [tj]
IGameEvent * event = gameeventmanager->CreateEvent( "achievement_earned_local" );
if ( event )
{
event->SetInt( "achievement", pAchievement->GetAchievementID() );
gameeventmanager->FireEventClientSide( event );
}
//=============================================================================
// HPE_END
//=============================================================================
if ( cc_achievement_debug.GetInt() > 0 )
{
Msg( "Achievement awarded: %s\n", pAchievement->GetName() );
}
// save state at next good opportunity. (Don't do it immediately, may hitch at bad time.)
SetDirty( true );
if ( IsPC() )
{
#ifndef NO_STEAM
if ( steamapicontext->SteamUserStats() )
{
VPROF_BUDGET( "AwardAchievement", VPROF_BUDGETGROUP_STEAM );
// set this achieved in the Steam client
bool bRet = steamapicontext->SteamUserStats()->SetAchievement( pAchievement->GetName() );
// Assert( bRet );
if ( bRet )
{
m_AchievementsAwarded.AddToTail( iAchievementID );
}
}
#endif
}
else if ( IsX360() )
{
#ifdef _X360
if ( xboxsystem )
xboxsystem->AwardAchievement( XBX_GetPrimaryUserId(), iAchievementID );
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose: updates specified achievement
//-----------------------------------------------------------------------------
void CAchievementMgr::UpdateAchievement( int iAchievementID, int nData )
{
CBaseAchievement *pAchievement = GetAchievementByID( iAchievementID );
Assert( pAchievement );
if ( !pAchievement )
return;
if ( !pAchievement->AlwaysEnabled() && !CheckAchievementsEnabled() )
{
Msg( "Achievements disabled, ignoring achievement update for %s\n", pAchievement->GetName() );
return;
}
if ( pAchievement->IsAchieved() )
{
if ( cc_achievement_debug.GetInt() > 0 )
{
Msg( "Achievement update called but already achieved: %s\n", pAchievement->GetName() );
}
return;
}