You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
625 lines
18 KiB
625 lines
18 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose: game stat gathering
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "tf_stats.h"
|
||
|
#include "tf_shareddefs.h"
|
||
|
#include "tf_team.h"
|
||
|
#include "tf_player.h"
|
||
|
#include "utlbuffer.h"
|
||
|
#include "filesystem.h"
|
||
|
#include "igamesystem.h"
|
||
|
#include "textstatsmgr.h"
|
||
|
#include "info_act.h"
|
||
|
|
||
|
static ConVar tf_stats( "tf_stats", "0", 0, "Enable stat gathering for TF2." );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Collect stats every N seconds
|
||
|
//-----------------------------------------------------------------------------
|
||
|
#define TF_STATS_COLLECTION_TIME 1
|
||
|
|
||
|
#define TF_STAT_FILE "tf_stat_total"
|
||
|
#define TF_TEAM_STAT_FILE "tf_stat_team"
|
||
|
#define TF_PLAYER_STAT_FILE "tf_stat_class"
|
||
|
|
||
|
static char s_pStatFile[MAX_PATH];
|
||
|
static char s_pTeamStatFile[MAX_PATH];
|
||
|
static char s_pPlayerStatFile[MAX_PATH];
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Strings assocaited with the stats
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static const char *s_pStatStrings[TF_STAT_COUNT] =
|
||
|
{
|
||
|
"Ferry Control", // TF_STAT_FERRY_CONTROL
|
||
|
"Resource Chunks Spawned", // TF_STAT_RESOURCE_CHUNKS_SPAWNED,
|
||
|
"Resource Gems Spawned", // TF_STAT_RESOURCE_PROCESSED_CHUNKS_SPAWNED,
|
||
|
"Resource Chunks Retired", // TF_STAT_RESOURCE_CHUNKS_RETIRED,
|
||
|
};
|
||
|
|
||
|
// These are the strings for the team stats above TFCLASS_CLASS_COUNT.
|
||
|
static const char *s_pNonClassTeamStatStrings[TF_TEAM_STAT_COUNT-TFCLASS_CLASS_COUNT] =
|
||
|
{
|
||
|
"Player Count", // TF_TEAM_STAT_PLAYER_COUNT,
|
||
|
"Resources Collected", // TF_TEAM_STAT_RESOURCES_COLLECTED,
|
||
|
"Resources Harvested", // TF_TEAM_STAT_RESOURCES_HARVESTED,
|
||
|
"Chunks Dropped", // TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED,
|
||
|
"Chunks Collected", // TF_TEAM_STAT_RESOURCE_CHUNKS_COLLECTED,
|
||
|
"Kill Count", // TF_TEAM_STAT_KILL_COUNT,
|
||
|
"Destroyed Objects", // TF_TEAM_STAT_DESTROYED_OBJECT_COUNT,
|
||
|
"Ferry Control Time", // TF_TEAM_STAT_FERRY_CONTROL_TIME,
|
||
|
};
|
||
|
|
||
|
// These are initialized in the first call to GetTeamStatString().
|
||
|
static const char *s_pTeamStatStrings[TF_TEAM_STAT_COUNT];
|
||
|
static bool s_bTeamStatStringsInitted = false;
|
||
|
|
||
|
static const char *s_pPlayerStatStrings[TF_PLAYER_STAT_COUNT] =
|
||
|
{
|
||
|
"Player Count", // TF_PLAYER_STAT_PLAYER_COUNT
|
||
|
"Player Seconds", // TF_PLAYER_STAT_PLAYER_SECONDS
|
||
|
"Seconds At Least One Existed", // TF_PLAYER_STAT_EXISTING_SECONDS
|
||
|
|
||
|
"Resources Acquired", // TF_PLAYER_STAT_RESOURCES_ACQUIRED
|
||
|
"Resources Acquired From Chunks", // TF_PLAYER_STAT_RESOURCES_ACQUIRED_FROM_CHUNKS
|
||
|
"Resources Carried", // TF_PLAYER_STAT_RESOURCES_CARRIED,
|
||
|
"Resources Spent", // TF_PLAYER_STAT_RESOURCES_SPENT,
|
||
|
"Object Value", // TF_PLAYER_STAT_CURRENT_OBJECT_VALUE
|
||
|
"Objects Owned", // TF_PLAYER_STAT_OBJECT_COUNT,
|
||
|
|
||
|
"Kill Count", // TF_PLAYER_STAT_KILL_COUNT,
|
||
|
"Objects Destroyed", // TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT,
|
||
|
"Health Given", // TF_PLAYER_STAT_HEALTH_GIVEN,
|
||
|
|
||
|
"Animation Idle Time", // TF_PLAYER_STAT_ANIMATION_IDLE,
|
||
|
"Animation Walk Time", // TF_PLAYER_STAT_ANIMATION_WALKING,
|
||
|
"Animation Run Time", // TF_PLAYER_STAT_ANIMATION_RUNNING,
|
||
|
"Animation Crouch Time",// TF_PLAYER_STAT_ANIMATION_CROUCHING,
|
||
|
"Animation Jump Time", // TF_PLAYER_STAT_ANIMATION_JUMPING,
|
||
|
"Animation Other Time", // TF_PLAYER_STAT_ANIMATION_OTHER,
|
||
|
};
|
||
|
|
||
|
|
||
|
static const char *GetStatString( int stat )
|
||
|
{
|
||
|
if (stat < TF_STAT_FIRST_OBJECT_BUILT)
|
||
|
return s_pStatStrings[stat];
|
||
|
|
||
|
static char s_TempBuf[256];
|
||
|
Q_snprintf( s_TempBuf, sizeof( s_TempBuf ), "%s Count Built", GetObjectInfo( stat - TF_STAT_FIRST_OBJECT_BUILT )->m_pClassName );
|
||
|
return s_TempBuf;
|
||
|
}
|
||
|
|
||
|
static const char *GetTeamStatString( int stat )
|
||
|
{
|
||
|
if ( !s_bTeamStatStringsInitted )
|
||
|
{
|
||
|
s_bTeamStatStringsInitted = true;
|
||
|
|
||
|
// Go through and fill in the strings.
|
||
|
for ( int i=0; i < TFCLASS_CLASS_COUNT; i++ )
|
||
|
s_pTeamStatStrings[i] = GetTFClassInfo( i )->m_pClassName;
|
||
|
|
||
|
for ( i=TFCLASS_CLASS_COUNT; i < TF_TEAM_STAT_COUNT; i++ )
|
||
|
s_pTeamStatStrings[i] = s_pNonClassTeamStatStrings[i - TFCLASS_CLASS_COUNT];
|
||
|
}
|
||
|
|
||
|
return s_pTeamStatStrings[stat];
|
||
|
}
|
||
|
|
||
|
static const char *GetPlayerStatString( int stat )
|
||
|
{
|
||
|
return s_pPlayerStatStrings[stat];
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Implementation of the TF stats class
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CTFStats : public CAutoGameSystem, public ITFStats
|
||
|
{
|
||
|
public:
|
||
|
CTFStats();
|
||
|
|
||
|
// Inherited from IAutoServerSystem
|
||
|
virtual void LevelInitPreEntity();
|
||
|
virtual void FrameUpdatePostEntityThink( );
|
||
|
|
||
|
// Clear out the stats + their history
|
||
|
void ResetStats();
|
||
|
|
||
|
void IncrementStat( TFStatId_t stat, int nIncrement );
|
||
|
void SetStat( TFStatId_t stat, int nAmount );
|
||
|
|
||
|
void IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement );
|
||
|
void SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount );
|
||
|
|
||
|
void IncrementPlayerStat( CBaseEntity *pPlayer, TFPlayerStatId_t stat, int nIncrement );
|
||
|
void ClearPlayerStat( int nTeam, TFPlayerStatId_t stat );
|
||
|
|
||
|
// We need to be ticked once a frame
|
||
|
void FrameUpdate( );
|
||
|
|
||
|
private:
|
||
|
struct Stat_t
|
||
|
{
|
||
|
int m_nCount;
|
||
|
};
|
||
|
|
||
|
typedef const char * (*StatNameFunc_t)( int stat );
|
||
|
|
||
|
// Collects frame-based stats
|
||
|
void CollectFrameStats( );
|
||
|
void CollectStats( );
|
||
|
|
||
|
int GetStat( TFStatId_t stat ) const { return m_Stats[stat].m_nCount; }
|
||
|
int GetTeamStat( int nTeam, TFTeamStatId_t stat ) const { return m_TeamStats[nTeam][stat].m_nCount; }
|
||
|
void WriteHeader( CUtlBuffer &buf, const char *pPrefix, int nCount, StatNameFunc_t func, bool bTerminate = true );
|
||
|
void WriteStatLine( CUtlBuffer &buf, int nCount, Stat_t *pStats, bool bTerminate = true );
|
||
|
void WriteAvgStatLine( CUtlBuffer &buf );
|
||
|
void WriteStats();
|
||
|
void WriteTeamStats();
|
||
|
void WritePlayerStats( );
|
||
|
void EraseFile( const char *pFileName );
|
||
|
void AppendToFile( const char *pFileName, CUtlBuffer &buf );
|
||
|
void ComputeFileNames();
|
||
|
void ClearStats();
|
||
|
|
||
|
// Compute class-based stats from the player stats
|
||
|
int ComputeClassStats( int nTeam, TFClass classType, Stat_t *pStats );
|
||
|
|
||
|
bool m_bWrittenHeader;
|
||
|
int m_nLastWriteTime;
|
||
|
Stat_t m_Stats[TF_STAT_COUNT];
|
||
|
Stat_t m_TeamStats[MAX_TF_TEAMS][TF_TEAM_STAT_COUNT];
|
||
|
Stat_t m_ClassStats[MAX_TF_TEAMS][TFCLASS_CLASS_COUNT][TF_PLAYER_STAT_COUNT];
|
||
|
};
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Accessor method
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static CTFStats s_TFStats;
|
||
|
ITFStats *TFStats()
|
||
|
{
|
||
|
return &s_TFStats;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Constructor, destructor
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CTFStats::CTFStats()
|
||
|
{
|
||
|
ResetStats();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Clear out the stats + their history
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::ClearStats()
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < TF_STAT_COUNT; ++i)
|
||
|
{
|
||
|
SetStat( (TFStatId_t)i, 0 );
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < MAX_TF_TEAMS; ++i)
|
||
|
{
|
||
|
for (int j = 0; j < TF_TEAM_STAT_COUNT; ++j)
|
||
|
{
|
||
|
SetTeamStat(i, (TFTeamStatId_t)j, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam)
|
||
|
{
|
||
|
for (i = 0; i < TFCLASS_CLASS_COUNT; ++i)
|
||
|
{
|
||
|
for (int j = 0; j < TF_PLAYER_STAT_COUNT; ++j)
|
||
|
{
|
||
|
m_ClassStats[nTeam][i][(TFPlayerStatId_t)j].m_nCount = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Clear out the stats + their history
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::ResetStats()
|
||
|
{
|
||
|
ClearStats();
|
||
|
m_bWrittenHeader = false;
|
||
|
m_nLastWriteTime = -9999;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Inherited from IAutoServerSystem
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::LevelInitPreEntity()
|
||
|
{
|
||
|
ResetStats();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Update stats...
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::IncrementStat( TFStatId_t stat, int nIncrement )
|
||
|
{
|
||
|
m_Stats[stat].m_nCount += nIncrement;
|
||
|
}
|
||
|
|
||
|
void CTFStats::SetStat( TFStatId_t stat, int nAmount )
|
||
|
{
|
||
|
m_Stats[stat].m_nCount = nAmount;
|
||
|
}
|
||
|
|
||
|
void CTFStats::IncrementTeamStat( int nTeam, TFTeamStatId_t stat, int nIncrement )
|
||
|
{
|
||
|
m_TeamStats[nTeam][stat].m_nCount += nIncrement;
|
||
|
}
|
||
|
|
||
|
void CTFStats::SetTeamStat( int nTeam, TFTeamStatId_t stat, int nAmount )
|
||
|
{
|
||
|
m_TeamStats[nTeam][stat].m_nCount = nAmount;
|
||
|
}
|
||
|
|
||
|
void CTFStats::IncrementPlayerStat( CBaseEntity *pEntity, TFPlayerStatId_t stat, int nIncrement )
|
||
|
{
|
||
|
if (!pEntity->IsPlayer())
|
||
|
return;
|
||
|
|
||
|
CBaseTFPlayer *pTFPlayer = static_cast<CBaseTFPlayer*>(pEntity);
|
||
|
int nTeam = pTFPlayer->GetTeamNumber();
|
||
|
CPlayerClass *pPlayerClass = pTFPlayer->GetPlayerClass();
|
||
|
int nClass = pPlayerClass ? pPlayerClass->GetTFClass() : TFCLASS_UNDECIDED;
|
||
|
|
||
|
m_ClassStats[nTeam][nClass][stat].m_nCount += nIncrement;
|
||
|
}
|
||
|
|
||
|
void CTFStats::ClearPlayerStat( int nTeam, TFPlayerStatId_t stat )
|
||
|
{
|
||
|
for (int i = 0; i < TFCLASS_CLASS_COUNT; ++i)
|
||
|
{
|
||
|
m_ClassStats[nTeam][i][stat].m_nCount = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// We need to be ticked once a frame
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::CollectFrameStats( )
|
||
|
{
|
||
|
// This collects a bunch of polled stats so we don't have to pollute
|
||
|
// a bunch of code
|
||
|
for (int i = 0; i < MAX_TF_TEAMS; ++i)
|
||
|
{
|
||
|
CTFTeam *pTeam = GetGlobalTFTeam(i);
|
||
|
for (int j = pTeam->GetNumPlayers(); --j >= 0; )
|
||
|
{
|
||
|
CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pTeam->GetPlayer(j));
|
||
|
if (!pPlayer)
|
||
|
continue;
|
||
|
|
||
|
int nTimeMS = 1000 * gpGlobals->frametime;
|
||
|
|
||
|
switch( pPlayer->GetActivity() )
|
||
|
{
|
||
|
case ACT_IDLE:
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_IDLE, nTimeMS );
|
||
|
break;
|
||
|
|
||
|
case ACT_WALK:
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_WALKING, nTimeMS );
|
||
|
break;
|
||
|
|
||
|
case ACT_RUN:
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_RUNNING, nTimeMS );
|
||
|
break;
|
||
|
|
||
|
case ACT_JUMP:
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_JUMPING, nTimeMS );
|
||
|
break;
|
||
|
|
||
|
case ACT_CROUCHIDLE:
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_CROUCHING, nTimeMS );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_ANIMATION_OTHER, nTimeMS );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// We need to be ticked once a frame
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::CollectStats( )
|
||
|
{
|
||
|
// This collects a bunch of polled stats so we don't have to pollute
|
||
|
// a bunch of code
|
||
|
bool bAtLeastOnePlayer = false;
|
||
|
for (int i = 0; i < MAX_TF_TEAMS; ++i)
|
||
|
{
|
||
|
CTFTeam *pTeam = GetGlobalTFTeam(i);
|
||
|
|
||
|
for ( int iClass=0; iClass < TFCLASS_CLASS_COUNT; iClass++ )
|
||
|
{
|
||
|
SetTeamStat( i, (TFTeamStatId_t)iClass, pTeam->GetNumOfClass( (TFClass)iClass ) );
|
||
|
}
|
||
|
|
||
|
SetTeamStat( i, TF_TEAM_STAT_PLAYER_COUNT, pTeam->GetNumPlayers() );
|
||
|
|
||
|
if ( GetStat( TF_STAT_FERRY_CONTROL ) == i )
|
||
|
{
|
||
|
IncrementTeamStat( i, TF_TEAM_STAT_FERRY_CONTROL_TIME, TF_STATS_COLLECTION_TIME );
|
||
|
}
|
||
|
|
||
|
ClearPlayerStat( i, TF_PLAYER_STAT_OBJECT_COUNT );
|
||
|
ClearPlayerStat( i, TF_PLAYER_STAT_RESOURCES_CARRIED );
|
||
|
ClearPlayerStat( i, TF_PLAYER_STAT_CURRENT_OBJECT_VALUE );
|
||
|
ClearPlayerStat( i, TF_PLAYER_STAT_PLAYER_COUNT );
|
||
|
|
||
|
bool bClassEncountered[TFCLASS_CLASS_COUNT];
|
||
|
memset( bClassEncountered, 0, TFCLASS_CLASS_COUNT * sizeof(bool) );
|
||
|
for (int j = pTeam->GetNumPlayers(); --j >= 0; )
|
||
|
{
|
||
|
bAtLeastOnePlayer = true;
|
||
|
|
||
|
CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pTeam->GetPlayer(j));
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_OBJECT_COUNT, pPlayer->GetObjectCount() );
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_CARRIED, pPlayer->GetBankResources() );
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_PLAYER_COUNT, 1 );
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_PLAYER_SECONDS, 1 );
|
||
|
|
||
|
CPlayerClass *pPlayerClass = pPlayer->GetPlayerClass();
|
||
|
int nClass = pPlayerClass ? pPlayerClass->GetTFClass() : TFCLASS_UNDECIDED;
|
||
|
|
||
|
if (!bClassEncountered[nClass])
|
||
|
{
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_EXISTING_SECONDS, 1 );
|
||
|
bClassEncountered[nClass] = true;
|
||
|
}
|
||
|
|
||
|
// Count up the cost of all current objects..
|
||
|
int nCost = 0;
|
||
|
int pObjectCount[OBJ_LAST];
|
||
|
memset( pObjectCount, 0, OBJ_LAST * sizeof(int) );
|
||
|
for (int k = pPlayer->GetObjectCount(); --k >= 0; )
|
||
|
{
|
||
|
CBaseObject *pObject = pPlayer->GetObject(k);
|
||
|
if (pObject)
|
||
|
{
|
||
|
int nType = pObject->GetType();
|
||
|
nCost += CalculateObjectCost( nType, pObjectCount[nType], pPlayer->GetTeamNumber(), false );
|
||
|
++pObjectCount[nType];
|
||
|
}
|
||
|
}
|
||
|
IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_CURRENT_OBJECT_VALUE, nCost );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Output : char const
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::ComputeFileNames()
|
||
|
{
|
||
|
Q_snprintf( s_pStatFile, sizeof( s_pStatFile ), "%s.txt", TF_STAT_FILE );
|
||
|
Q_snprintf( s_pTeamStatFile, sizeof( s_pTeamStatFile ), "%s.txt", TF_TEAM_STAT_FILE );
|
||
|
Q_snprintf( s_pPlayerStatFile, sizeof( s_pPlayerStatFile ), "%s.txt", TF_PLAYER_STAT_FILE );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// File access.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::EraseFile( const char *pFileName )
|
||
|
{
|
||
|
filesystem->RemoveFile( pFileName, "GAME" );
|
||
|
}
|
||
|
|
||
|
void CTFStats::AppendToFile( const char *pFileName, CUtlBuffer &buf )
|
||
|
{
|
||
|
FileHandle_t fh = filesystem->Open( pFileName, "a", "LOGDIR" );
|
||
|
if (fh != FILESYSTEM_INVALID_HANDLE)
|
||
|
{
|
||
|
filesystem->Write( buf.Base(), buf.TellPut(), fh );
|
||
|
filesystem->Close( fh );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Write out a header.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::WriteHeader( CUtlBuffer &buf, const char *pPrefix, int nCount, StatNameFunc_t func, bool bTerminate )
|
||
|
{
|
||
|
for (int i = 0; i < nCount-1; ++i)
|
||
|
{
|
||
|
buf.Printf("%s %s\t", pPrefix, func(i) );
|
||
|
}
|
||
|
|
||
|
buf.Printf( bTerminate ? "%s %s\n" : "%s %s\t", pPrefix, func(nCount-1) );
|
||
|
}
|
||
|
|
||
|
void CTFStats::WriteStatLine( CUtlBuffer &buf, int nCount, Stat_t *pStats, bool bTerminate )
|
||
|
{
|
||
|
for (int i = 0; i < nCount-1; ++i)
|
||
|
{
|
||
|
buf.Printf("%d\t", pStats[i].m_nCount );
|
||
|
}
|
||
|
|
||
|
buf.Printf( bTerminate ? "%d\n" : "%d\t", pStats[nCount-1].m_nCount );
|
||
|
}
|
||
|
|
||
|
void CTFStats::WriteAvgStatLine( CUtlBuffer &buf )
|
||
|
{
|
||
|
int nTeam, i, j;
|
||
|
for ( nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam )
|
||
|
{
|
||
|
for ( i = 0; i < TF_PLAYER_STAT_COUNT; ++i )
|
||
|
{
|
||
|
for ( j = 1; j < TFCLASS_CLASS_COUNT; ++j )
|
||
|
{
|
||
|
if (!GetTFClassInfo(j)->m_pCurrentlyActive)
|
||
|
continue;
|
||
|
|
||
|
buf.Printf("%d\t", m_ClassStats[nTeam][j][i].m_nCount);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Blat out that last tab and replace with a \n
|
||
|
buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
|
||
|
buf.Printf("\n");
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Write out total stats...
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::WriteStats()
|
||
|
{
|
||
|
CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER );
|
||
|
|
||
|
if (!m_bWrittenHeader)
|
||
|
{
|
||
|
EraseFile( s_pStatFile );
|
||
|
WriteHeader( buf, "", TF_STAT_COUNT, GetStatString );
|
||
|
}
|
||
|
|
||
|
WriteStatLine( buf, TF_STAT_COUNT, m_Stats );
|
||
|
AppendToFile( s_pStatFile, buf );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Write out total stats...
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::WriteTeamStats()
|
||
|
{
|
||
|
CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER );
|
||
|
|
||
|
int i,j;
|
||
|
if (!m_bWrittenHeader)
|
||
|
{
|
||
|
EraseFile( s_pTeamStatFile );
|
||
|
for ( i = 0; i < TF_TEAM_STAT_COUNT; ++i )
|
||
|
{
|
||
|
for ( j = 0; j < MAX_TF_TEAMS; ++j )
|
||
|
{
|
||
|
buf.Printf("Team %d %s\t", j, GetTeamStatString(i) );
|
||
|
}
|
||
|
}
|
||
|
// Blat out that last tab and replace with a \n
|
||
|
buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
|
||
|
buf.Printf("\n");
|
||
|
}
|
||
|
|
||
|
for ( i = 0; i < TF_TEAM_STAT_COUNT; ++i )
|
||
|
{
|
||
|
for ( j = 0; j < MAX_TF_TEAMS; ++j )
|
||
|
{
|
||
|
buf.Printf("%d\t", m_TeamStats[j][i].m_nCount );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Blat out that last tab and replace with a \n
|
||
|
buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
|
||
|
buf.Printf("\n");
|
||
|
|
||
|
AppendToFile( s_pTeamStatFile, buf );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Write out total stats...
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::WritePlayerStats( )
|
||
|
{
|
||
|
CUtlBuffer buf( 0, 1024, CUtlBuffer::TEXT_BUFFER );
|
||
|
|
||
|
int i, j, nTeam;
|
||
|
if (!m_bWrittenHeader)
|
||
|
{
|
||
|
EraseFile( s_pPlayerStatFile );
|
||
|
for ( nTeam = 0; nTeam < MAX_TF_TEAMS; ++nTeam )
|
||
|
{
|
||
|
for ( i = 0; i < TF_PLAYER_STAT_COUNT; ++i )
|
||
|
{
|
||
|
for ( j = 1; j < TFCLASS_CLASS_COUNT; ++j )
|
||
|
{
|
||
|
if (!GetTFClassInfo(j)->m_pCurrentlyActive)
|
||
|
continue;
|
||
|
|
||
|
buf.Printf("Team %d %s %s\t", nTeam, GetTFClassInfo( j )->m_pClassName, GetPlayerStatString(i) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Blat out that last tab and replace with a \n
|
||
|
buf.SeekPut( CUtlBuffer::SEEK_CURRENT, -1 );
|
||
|
buf.Printf("\n");
|
||
|
}
|
||
|
|
||
|
WriteAvgStatLine( buf );
|
||
|
|
||
|
AppendToFile( s_pPlayerStatFile, buf );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// We need to be ticked once a frame
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CTFStats::FrameUpdatePostEntityThink( )
|
||
|
{
|
||
|
if (!tf_stats.GetBool())
|
||
|
return;
|
||
|
|
||
|
// Don't stat gather during waiting acts
|
||
|
if ( CurrentActIsAWaitingAct() )
|
||
|
return;
|
||
|
|
||
|
CollectFrameStats();
|
||
|
|
||
|
// NOTE: We could keep track of the history here if we wanted for later
|
||
|
// display when the map ends
|
||
|
|
||
|
// Record the history every so often
|
||
|
if (gpGlobals->curtime - m_nLastWriteTime < TF_STATS_COLLECTION_TIME)
|
||
|
return;
|
||
|
|
||
|
if (!m_bWrittenHeader)
|
||
|
{
|
||
|
ComputeFileNames();
|
||
|
}
|
||
|
|
||
|
CollectStats();
|
||
|
WriteStats();
|
||
|
WriteTeamStats();
|
||
|
WritePlayerStats();
|
||
|
ClearStats();
|
||
|
|
||
|
// By this point, we've written the header for each file
|
||
|
m_bWrittenHeader = true;
|
||
|
|
||
|
m_nLastWriteTime = gpGlobals->curtime;
|
||
|
}
|