summaryrefslogtreecommitdiff
path: root/game/server/tf2/tf_stats.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf2/tf_stats.cpp')
-rw-r--r--game/server/tf2/tf_stats.cpp624
1 files changed, 624 insertions, 0 deletions
diff --git a/game/server/tf2/tf_stats.cpp b/game/server/tf2/tf_stats.cpp
new file mode 100644
index 0000000..f2e3f02
--- /dev/null
+++ b/game/server/tf2/tf_stats.cpp
@@ -0,0 +1,624 @@
+//========= 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;
+}