/* ** 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); }