/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: StatsCollector.cpp /////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Electronic Arts Pacific.
//
// Confidential Information
// Copyright (C) 2002 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// created: Jul 2002
//
// Filename: StatsCollector.cpp
//
// author: Chris Huybregts
//
// purpose: Convinience class to gather player stats
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
//-----------------------------------------------------------------------------
// USER INCLUDES //////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include "Common/StatsCollector.h"
#include "Common/FileSystem.h"
#include "Common/PlayerList.h"
#include "Common/Player.h"
#include "Common/GlobalData.h"
#include "Common/Money.h"
#include "GameLogic/Object.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/MapUtil.h"
#include "GameNetwork/NetworkUtil.h"
#include "GameNetwork/LANAPICallbacks.h"
//-----------------------------------------------------------------------------
// DEFINES ////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
StatsCollector *TheStatsCollector = NULL;
static char statsDir[255] = "Stats\\";
//-----------------------------------------------------------------------------
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// init all
//=============================================================================
StatsCollector::StatsCollector( void )
{
//Added By Sadullah Nader
//Initialization(s) inserted
m_isScrolling = FALSE;
m_scrollBeginTime = 0;
m_scrollTime = 0;
//
m_timeCount = 0;
m_buildCommands = 0;
m_moveCommands = 0;
m_attackCommands = 0;
m_scrollMapCommands = 0;
m_AIUnits = 0;
m_playerUnits = 0;
m_lastUpdate = 0;
m_startFrame = TheGameLogic->getFrame();
}
//Destructor
//=============================================================================
StatsCollector::~StatsCollector( void )
{
}
// Reset and create the file header
//=============================================================================
void StatsCollector::reset( void )
{
// make sure we have a stats Dir.
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_saveStats)
{
AsciiString playtestDir = TheGlobalData->m_baseStatsDir;
playtestDir.concat(statsDir);
if (TheNetwork)
{
if (TheLAN)
{
TheFileSystem->createDirectory(playtestDir);
}
}
}
#endif
TheFileSystem->createDirectory(AsciiString(statsDir));
createFileName();
writeInitialFileInfo();
// zero out
zeroOutStats();
m_lastUpdate = TheGameLogic->getFrame(); // timeGetTime();
}
// Msgs pass through here so we can track whichever ones we want
//=============================================================================
void StatsCollector::collectMsgStats( const GameMessage *msg )
{
// We only care about our own messages.
if(ThePlayerList->getLocalPlayer()->getPlayerIndex() != msg->getPlayerIndex())
return;
switch (msg->getType())
{
case GameMessage::MSG_QUEUE_UNIT_CREATE:
case GameMessage::MSG_DOZER_CONSTRUCT:
case GameMessage::MSG_DOZER_CONSTRUCT_LINE:
{
++m_buildCommands;
break;
}
}
}
//Loop through all objects and count up the ones we want. (Very Slow!!!)
//=============================================================================
void StatsCollector::collectUnitCountStats( void )
{
for(Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject())
{
if((!(obj->isKindOf(KINDOF_INFANTRY) || obj->isKindOf(KINDOF_VEHICLE))) || ( obj->isNeutralControlled()) ||(obj->getControllingPlayer()->getSide().compare("Civilian") == 0))
continue;
if(obj->getControllingPlayer()->isLocalPlayer())
{
++m_playerUnits;
}
else
{
++m_AIUnits;
}
}
}
// call every frame and only do stuff when our time is up
//=============================================================================
void StatsCollector::update( void )
{
if(m_lastUpdate + (TheGlobalData->m_playStats * LOGICFRAMES_PER_SECOND) > TheGameLogic->getFrame())
return;
collectUnitCountStats();
if(m_isScrolling)
{
m_scrollTime += TheGameLogic->getFrame() - m_scrollBeginTime;
m_scrollBeginTime = TheGameLogic->getFrame();
}
m_timeCount += TheGlobalData->m_playStats;
writeStatInfo();
zeroOutStats();
m_lastUpdate = TheGameLogic->getFrame(); //timeGetTime();
}
void StatsCollector::incrementScrollMoveCount( void )
{
++m_scrollMapCommands;
}
void StatsCollector::incrementAttackCount( void )
{
++m_attackCommands;
}
void StatsCollector::incrementBuildCount( void )
{
++m_buildCommands;
}
void StatsCollector::incrementMoveCount( void )
{
++m_moveCommands;
}
void StatsCollector::writeFileEnd( void )
{
//open the file
FILE *f = fopen(m_statsFileName.str(), "a");
if(!f)
{
DEBUG_ASSERTCRASH(f, ("Unable to open file %s to write", m_statsFileName.str()));
return;
}
m_timeCount += (TheGameLogic->getFrame() - m_lastUpdate) / LOGICFRAMES_PER_SECOND;
writeStatInfo();
fprintf(f, "---------------------------------------------------\n");
// Time
struct tm *newTime;
time_t aclock;
time( &aclock );
newTime = localtime( &aclock );
fprintf(f, "End Time:\t%s\n",asctime(newTime) );
fprintf(f, "=KEY===============================================\n");
fprintf(f, "Time* = The Time Interval\n");
fprintf(f, "BC = Build Commands\n");
fprintf(f, "MC = Move Commands\n");
fprintf(f, "AC = Attack Commands\n");
fprintf(f, "SMC = Scroll Map Commands\n");
fprintf(f, "ST* = Scroll Time in Seconds\n");
fprintf(f, "OC = Other Commands (N/A)\n");
fprintf(f, "$$$ = Local Player's Cash Amount\n");
fprintf(f, "#PU = # of Player's Units\n");
fprintf(f, "#AIU = # of AI's Units\n");
fprintf(f, "===================================================\n");
fprintf(f, "* Times are in Game Seconds which are based off of frames. Current fps is set to %d\n", LOGICFRAMES_PER_SECOND);
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_benchmarkTimer > 0)
{
fprintf(f, "\n*** BENCHMARK MODE STATS ***\n");
fprintf(f, " Frames = %d\n", TheGameLogic->getFrame()-m_startFrame);
fprintf(f, "Seconds = %d\n", TheGlobalData->m_benchmarkTimer);
fprintf(f, " FPS = %.2f\n", ((Real)TheGameLogic->getFrame()-(Real)m_startFrame)/(Real)TheGlobalData->m_benchmarkTimer);
}
#endif
fclose(f);
}
void StatsCollector::startScrollTime( void )
{
m_isScrolling = TRUE;
m_scrollBeginTime = TheGameLogic->getFrame();
++m_scrollMapCommands;
}
void StatsCollector::endScrollTime( void )
{
if(!m_isScrolling)
return;
m_isScrolling = FALSE;
m_scrollTime += TheGameLogic->getFrame() - m_scrollBeginTime;
}
//-----------------------------------------------------------------------------
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
void StatsCollector::zeroOutStats( void )
{
m_buildCommands = 0;
m_moveCommands = 0;
m_attackCommands = 0;
m_scrollMapCommands = 0;
m_AIUnits = 0;
m_playerUnits = 0;
m_scrollTime = 0;
}
// create the filename based off of map time and date
//=============================================================================
void StatsCollector::createFileName( void )
{
m_statsFileName.clear();
// Date and Time
char datestr[256] = "";
time_t longTime;
struct tm *curtime;
time(&longTime);
curtime = localtime(&longTime);
strftime(datestr, 256, "_%b%d_%I%M%p", curtime);
// const MapMetaData *m = TheMapCache->findMap(TheGlobalData->m_mapName);
AsciiString name = TheGlobalData->m_mapName;
const char *fname = name.reverseFind('\\');
if (fname)
name = fname+1;
name.removeLastChar(); // p
name.removeLastChar(); // a
name.removeLastChar(); // m
name.removeLastChar(); // .
m_statsFileName.clear();
#if defined(_DEBUG) || defined(_INTERNAL)
if (TheGlobalData->m_saveStats)
{
m_statsFileName.set(TheGlobalData->m_baseStatsDir);
m_statsFileName.concat(statsDir);
if (TheNetwork)
{
if (TheLAN)
{
GameInfo *game = TheLAN->GetMyGame();
AsciiString players;
AsciiString full;
AsciiString fullPlusNum;
for (Int i=0; igetSlot(i);
if (slot && slot->isHuman())
{
AsciiString player;
player.format("%ls_", slot->getName().str());
players.concat(player);
}
}
full.format("%s%s_%d_%d", players.str(), name.str(), game->getSeed(), game->getLocalSlotNum());
AsciiString testString;
testString.format("%s%s.txt", m_statsFileName.str(), full.str());
m_statsFileName = testString;
}
}
else
{
m_statsFileName.format("%s%s%s.txt",statsDir, name.str(),datestr);
}
}
else
#endif
{
m_statsFileName.format("%s%s%s.txt",statsDir, name.str(),datestr);
}
}
// create the header of the file
//=============================================================================
void StatsCollector::writeInitialFileInfo()
{
//open the file
FILE *f = fopen(m_statsFileName.str(), "w");
if(!f)
{
DEBUG_ASSERTCRASH(f, ("Unable to open file %s to write", m_statsFileName.str()));
return;
}
fprintf(f, "---------------------------------------------------\n");
// Time
struct tm *newTime;
time_t aclock;
time( &aclock );
newTime = localtime( &aclock );
fprintf(f, "Date:\t%s",asctime(newTime) );
// Map
fprintf(f, "Map:\t%s\n", TheGlobalData->m_mapName.str());
// Side
fprintf(f, "Side:\t%s\n", ThePlayerList->getLocalPlayer()->getSide().str());
fprintf(f, "---------------------------------------------------\n\n");
fprintf(f, "Time*\tBC\tMC\tAC\tSMC\tST*\tOC\t$$$\t#PU\t#AIU\n");
collectUnitCountStats();
Money *m = ThePlayerList->getLocalPlayer()->getMoney();
fprintf(f, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", 0, m_buildCommands, m_moveCommands, m_attackCommands,
m_scrollMapCommands, 0 ,/*other commands*/0, m->countMoney(),
m_playerUnits, m_AIUnits );
// initial stats
// we don't want a file pointer open for seconds on end... we'll open it each time.
fclose(f);
}
// Write out the stats
//=============================================================================
void StatsCollector::writeStatInfo()
{
//open the file
FILE *f = fopen(m_statsFileName.str(), "a");
if(!f)
{
DEBUG_ASSERTCRASH(f, ("Unable to open file %s to write", m_statsFileName.str()));
return;
}
Money *m = ThePlayerList->getLocalPlayer()->getMoney();
fprintf(f, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", m_timeCount, m_buildCommands, m_moveCommands, m_attackCommands,
m_scrollMapCommands, m_scrollTime / LOGICFRAMES_PER_SECOND, /*other commands*/0,m->countMoney() ,
m_playerUnits, m_AIUnits );
fclose(f);
}