//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #ifndef GAMESTATS_H #define GAMESTATS_H #ifdef _WIN32 #pragma once #endif #include "igamesystem.h" #include "tier1/utlbuffer.h" #include "tier1/utldict.h" //#include "steamworks_gamestats.h" const int GAMESTATS_VERSION = 1; enum StatSendType_t { STATSEND_LEVELSHUTDOWN, STATSEND_APPSHUTDOWN, STATSEND_NOTENOUGHPLAYERS, }; struct StatsBufferRecord_t { float m_flFrameRate; // fps float m_flServerPing; // client ping to server }; #define STATS_WINDOW_SIZE (60 * 10) // # of records to hold #define STATS_RECORD_INTERVAL \ 1 // # of seconds between data grabs. 2 * 300 = every 10 minutes class CGameStats; void UpdatePerfStats(void); void SetGameStatsHandler(CGameStats *pGameStats); class CBasePlayer; class CPropVehicleDriveable; class CTakeDamageInfo; #ifdef GAME_DLL #define GAMESTATS_STANDARD_NOT_SAVED 0xFEEDBEEF enum GameStatsVersions_t { GAMESTATS_FILE_VERSION_OLD = 001, GAMESTATS_FILE_VERSION_OLD2, GAMESTATS_FILE_VERSION_OLD3, GAMESTATS_FILE_VERSION_OLD4, GAMESTATS_FILE_VERSION_OLD5, GAMESTATS_FILE_VERSION }; struct BasicGameStatsRecord_t { public: BasicGameStatsRecord_t() : m_nCount(0), m_nSeconds(0), m_nCommentary(0), m_nHDR(0), m_nCaptions(0), m_bSteam(true), m_bCyberCafe(false), m_nDeaths(0) { Q_memset(m_nSkill, 0, sizeof(m_nSkill)); } void Clear(); void SaveToBuffer(CUtlBuffer &buf); bool ParseFromBuffer(CUtlBuffer &buf, int iBufferStatsVersion); // Data public: int m_nCount; int m_nSeconds; int m_nCommentary; int m_nHDR; int m_nCaptions; int m_nSkill[3]; bool m_bSteam; bool m_bCyberCafe; int m_nDeaths; }; struct BasicGameStats_t { public: BasicGameStats_t() : m_nSecondsToCompleteGame(0), m_nHL2ChaptureUnlocked(0), m_bSteam(true), m_bCyberCafe(false), m_nDXLevel(0) {} void Clear(); void SaveToBuffer(CUtlBuffer &buf); bool ParseFromBuffer(CUtlBuffer &buf, int iBufferStatsVersion); BasicGameStatsRecord_t *FindOrAddRecordForMap(char const *mapname); // Data public: int m_nSecondsToCompleteGame; // 0 means they haven't finished playing yet BasicGameStatsRecord_t m_Summary; // Summary record CUtlDict m_MapTotals; bool m_bSteam; bool m_bCyberCafe; int m_nHL2ChaptureUnlocked; int m_nDXLevel; }; #endif // GAME_DLL class CBaseGameStats { public: CBaseGameStats(); // override this to declare what format you want to send. New products // should use new format. virtual bool UseOldFormat() { #ifdef GAME_DLL return true; // servers by default send old format for backward compat #else return false; // clients never used old format so no backward compat // issues, they use new format by default #endif } // Implement this if you support new format gamestats. // Return true if you added data to KeyValues, false if you have no data to // report virtual bool AddDataForSend(KeyValues *pKV, StatSendType_t sendType) { return false; } // These methods used for new format gamestats only and control when data // gets sent. virtual bool ShouldSendDataOnLevelShutdown() { // by default, servers send data at every level change and clients don't #ifdef GAME_DLL return true; #else return false; #endif } virtual bool ShouldSendDataOnAppShutdown() { // by default, clients send data at app shutdown and servers don't #ifdef GAME_DLL return false; #else return true; #endif } virtual void Event_Init(void); virtual void Event_Shutdown(void); virtual void Event_MapChange(const char *szOldMapName, const char *szNewMapName); virtual void Event_LevelInit(void); virtual void Event_LevelShutdown(float flElapsed); virtual void Event_SaveGame(void); virtual void Event_LoadGame(void); void CollectData(StatSendType_t sendType); void SendData(); void StatsLog(PRINTF_FORMAT_STRING char const *fmt, ...); // This is the first call made, so that we can "subclass" the CBaseGameStats // based on gamedir as needed (e.g., ep2 vs. episodic) virtual CBaseGameStats *OnInit(CBaseGameStats *pCurrentGameStats, char const *gamedir) { return pCurrentGameStats; } // Frees up data from gamestats and resets it to a clean state. virtual void Clear(void); virtual bool StatTrackingEnabledForMod(void) { return false; } // Override this to turn on the system. Stat tracking is disabled by // default and will always be disabled at the user's request static bool StatTrackingAllowed(void); // query whether stat tracking is // possible and warranted by the user virtual bool HaveValidData(void) { return true; } // whether we currently have an interesting enough data set to upload. // Called at upload time; if false, data is not uploaded. virtual bool ShouldTrackStandardStats(void) { return true; } // exactly what was tracked for EP1 release // Get mod specific strings used for tracking, defaults should work fine for // most cases virtual const char *GetStatSaveFileName(void); virtual const char *GetStatUploadRegistryKeyName(void); const char *GetUserPseudoUniqueID(void); virtual bool UserPlayedAllTheMaps(void) { return false; } // be sure to override this to determine user completion time #ifdef CLIENT_DLL virtual void Event_AchievementProgress(int achievementID, const char *achievementName) {} #endif #ifdef GAME_DLL virtual void Event_PlayerKilled(CBasePlayer *pPlayer, const CTakeDamageInfo &info); virtual void Event_PlayerConnected(CBasePlayer *pBasePlayer); virtual void Event_PlayerDisconnected(CBasePlayer *pBasePlayer); virtual void Event_PlayerDamage(CBasePlayer *pBasePlayer, const CTakeDamageInfo &info); virtual void Event_PlayerKilledOther(CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info); virtual void Event_PlayerSuicide(CBasePlayer *pPlayer) {} virtual void Event_Credits(); virtual void Event_Commentary(); virtual void Event_CrateSmashed(); virtual void Event_Punted(CBaseEntity *pObject); virtual void Event_PlayerTraveled(CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting); virtual void Event_WeaponFired(CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName); virtual void Event_WeaponHit(CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info); virtual void Event_FlippedVehicle(CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle); virtual void Event_PreSaveGameLoaded(char const *pSaveName, bool bInGame); virtual void Event_PlayerEnteredGodMode(CBasePlayer *pBasePlayer); virtual void Event_PlayerEnteredNoClip(CBasePlayer *pBasePlayer); virtual void Event_DecrementPlayerEnteredNoClip(CBasePlayer *pBasePlayer); virtual void Event_IncrementCountedStatistic(const Vector &vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount); //============================================================================= // HPE_BEGIN // [dwenger] Functions necessary for cs-specific stats //============================================================================= virtual void Event_WindowShattered(CBasePlayer *pPlayer); //============================================================================= // HPE_END //============================================================================= // custom data to tack onto existing stats if you're not doing a complete // overhaul virtual void AppendCustomDataToSaveBuffer(CUtlBuffer &SaveBuffer) { } // custom data you want thrown into the default save and upload path virtual void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer){}; // when loading the saved stats file, this // will point to where you started saving // data to the save buffer virtual void LoadingEvent_PlayerIDDifferentThanLoadedStats( void); // Only called if you use the base SaveToFileNOW() and // LoadFromFile() functions. Used in case you want to // keep/invalidate data that was just loaded. virtual bool LoadFromFile(void); // called just before Event_Init() virtual bool SaveToFileNOW( bool bForceSyncWrite = false); // saves buffers to their respective // files now, returns success or failure virtual bool UploadStatsFileNOW( void); // uploads data to the CSER now, returns success or failure static bool AppendLump(int nMaxLumpCount, CUtlBuffer &SaveBuffer, unsigned short iLump, unsigned short iLumpCount, size_t nSize, void *pData); static bool GetLumpHeader(int nMaxLumpCount, CUtlBuffer &LoadBuffer, unsigned short &iLump, unsigned short &iLumpCount, bool bPermissive = false); static void LoadLump(CUtlBuffer &LoadBuffer, unsigned short iLumpCount, size_t nSize, void *pData); // default save behavior is to save on level shutdown, and game shutdown virtual bool AutoSave_OnInit(void) { return false; } virtual bool AutoSave_OnShutdown(void) { return true; } virtual bool AutoSave_OnMapChange(void) { return false; } virtual bool AutoSave_OnLevelInit(void) { return false; } virtual bool AutoSave_OnLevelShutdown(void) { return true; } // default upload behavior is to upload on game shutdown virtual bool AutoUpload_OnInit(void) { return false; } virtual bool AutoUpload_OnShutdown(void) { return true; } virtual bool AutoUpload_OnMapChange(void) { return false; } virtual bool AutoUpload_OnLevelInit(void) { return false; } virtual bool AutoUpload_OnLevelShutdown(void) { return false; } // Helper for builtin stuff void SetSteamStatistic(bool bUsingSteam); void SetCyberCafeStatistic(bool bIsCyberCafeUser); void SetHDRStatistic(bool bHDREnabled); void SetCaptionsStatistic(bool bClosedCaptionsEnabled); void SetSkillStatistic(int iSkillSetting); void SetDXLevelStatistic(int iDXLevel); void SetHL2UnlockedChapterStatistic(void); #endif // GAMEDLL public: #ifdef GAME_DLL BasicGameStats_t m_BasicStats; // exposed in case you do a complete // overhaul and still want to save it #endif bool m_bLogging : 1; bool m_bLoggingToFile : 1; }; #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: // Input : &SaveBuffer - // iLump - // iLumpCount - //----------------------------------------------------------------------------- inline bool CBaseGameStats::AppendLump(int nMaxLumpCount, CUtlBuffer &SaveBuffer, unsigned short iLump, unsigned short iLumpCount, size_t nSize, void *pData) { // Verify the lump index. Assert((iLump > 0) && (iLump < nMaxLumpCount)); if (!((iLump > 0) && (iLump < nMaxLumpCount))) return false; // Check to see if we have any elements to save. if (iLumpCount <= 0) return false; // Write the lump id and element count. SaveBuffer.PutUnsignedShort(iLump); SaveBuffer.PutUnsignedShort(iLumpCount); size_t nTotalSize = iLumpCount * nSize; SaveBuffer.Put(pData, nTotalSize); return true; } //----------------------------------------------------------------------------- // Purpose: // Input : &LoadBuffer - // &iLump - // &iLumpCount - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- inline bool CBaseGameStats::GetLumpHeader(int nMaxLumpCount, CUtlBuffer &LoadBuffer, unsigned short &iLump, unsigned short &iLumpCount, bool bPermissive /*= false*/) { // Get the lump id and element count. iLump = LoadBuffer.GetUnsignedShort(); if (!LoadBuffer.IsValid()) { // check for EOF return false; } iLumpCount = LoadBuffer.GetUnsignedShort(); if (bPermissive) return true; // Verify the lump index. Assert((iLump > 0) && (iLump < nMaxLumpCount)); if (!((iLump > 0) && (iLump < nMaxLumpCount))) { return false; } // Check to see if we have any elements to save. if (iLumpCount <= 0) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Loads 1 or more lumps of raw data // Input : &LoadBuffer - buffer to be read from // iLumpCount - # of lumps to read // nSize - size of each lump // pData - where to store the data //----------------------------------------------------------------------------- inline void CBaseGameStats::LoadLump(CUtlBuffer &LoadBuffer, unsigned short iLumpCount, size_t nSize, void *pData) { LoadBuffer.Get(pData, iLumpCount * nSize); } #endif // GAME_DLL // Moving the extern out of the GAME_DLL block so that the client can access it extern CBaseGameStats *gamestats; // starts out pointing at a singleton of the // class above, overriding this in any // constructor should work for replacing it // used to drive most of the game stat event handlers as well as track basic // stats under the hood of CBaseGameStats class CBaseGameStats_Driver : public CAutoGameSystemPerFrame { public: CBaseGameStats_Driver(void); typedef CAutoGameSystemPerFrame BaseClass; // IGameSystem overloads virtual bool Init(); virtual void Shutdown(); // Level init, shutdown virtual void LevelInitPreEntity(); virtual void LevelShutdownPreEntity(); virtual void LevelShutdownPreClearSteamAPIContext(); virtual void LevelShutdown(); // Called during game save virtual void OnSave(); // Called during game restore, after the local player has connected and // entities have been fully restored virtual void OnRestore(); virtual void FrameUpdatePostEntityThink(); void PossibleMapChange(void); void CollectData(StatSendType_t sendType); void SendData(); void ResetData(); bool AddBaseDataForSend(KeyValues *pKV, StatSendType_t sendType); StatsBufferRecord_t m_StatsBuffer[STATS_WINDOW_SIZE]; bool m_bBufferFull; int m_nWriteIndex; float m_flLastRealTime; float m_flLastSampleTime; float m_flTotalTimeInLevels; int m_iNumLevels; bool m_bDidVoiceChat; // Did the player use voice chat at ALL this map? template T AverageStat(T StatsBufferRecord_t::*field) const { T sum = 0; for (int i = 0; i < STATS_WINDOW_SIZE; i++) sum += m_StatsBuffer[i].*field; return sum / STATS_WINDOW_SIZE; } template T MaxStat(T StatsBufferRecord_t::*field) const { T maxsofar = -16000000; for (int i = 0; i < STATS_WINDOW_SIZE; i++) maxsofar = MAX(maxsofar, m_StatsBuffer[i].*field); return maxsofar; } template T MinStat(T StatsBufferRecord_t::*field) const { T minsofar = 16000000; for (int i = 0; i < STATS_WINDOW_SIZE; i++) minsofar = MIN(minsofar, m_StatsBuffer[i].*field); return minsofar; } inline void AdvanceIndex(void) { m_nWriteIndex++; if (m_nWriteIndex == STATS_WINDOW_SIZE) { m_nWriteIndex = 0; m_bBufferFull = true; } } void UpdatePerfStats(void); CUtlString m_PrevMapName; // used to track "OnMapChange" events int m_iLoadedVersion; char m_szLoadedUserID[17]; // GUID bool m_bEnabled; // false if incapable of uploading or the user doesn't // want to enable stat tracking bool m_bShuttingDown; bool m_bInLevel; bool m_bFirstLevel; time_t m_tLastUpload; float m_flLevelStartTime; bool m_bStationary; float m_flLastMovementTime; CUserCmd m_LastUserCmd; bool m_bGamePaused; float m_flPauseStartTime; CGamestatsData *m_pGamestatsData; }; #endif // GAMESTATS_H