This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
2020-08-04 13:13:01 -04:00

501 lines
17 KiB
C++

//========= 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<BasicGameStatsRecord_t, unsigned short> 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 <class T>
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 <class T>
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 <class T>
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