Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Core/GameEngine/Source/Common/ReplaySimulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
#include "Common/ReplaySimulation.h"

#include "Common/GameEngine.h"
#include "Common/GlobalData.h"
#include "Common/LocalFileSystem.h"
#include "Common/Recorder.h"
#include "Common/StatsExporter.h"
#include "Common/WorkerProcess.h"
#include "GameLogic/GameLogic.h"
#include "GameClient/GameClient.h"
Expand Down Expand Up @@ -81,6 +83,8 @@ int ReplaySimulation::simulateReplaysInThisProcess(const std::vector<AsciiString
printf("Simulating Replay \"%s\"\n", filename.str());
fflush(stdout);
DWORD startTimeMillis = GetTickCount();
if (TheGlobalData->m_exportStats)
StatsExporterBeginRecording();
if (TheRecorder->simulateReplay(filename))
{
UnsignedInt totalTimeSec = TheRecorder->getPlaybackFrameCount() / LOGICFRAMES_PER_SECOND;
Expand All @@ -99,6 +103,8 @@ int ReplaySimulation::simulateReplaysInThisProcess(const std::vector<AsciiString
fflush(stdout);
}
TheGameLogic->UPDATE();
if (TheGlobalData->m_exportStats)
StatsExporterCollectSnapshot();
if (TheRecorder->sawCRCMismatch())
{
numErrors++;
Expand All @@ -110,6 +116,8 @@ int ReplaySimulation::simulateReplaysInThisProcess(const std::vector<AsciiString
printf("Elapsed Time: %02d:%02d Game Time: %02d:%02d/%02d:%02d\n",
realTimeSec/60, realTimeSec%60, gameTimeSec/60, gameTimeSec%60, totalTimeSec/60, totalTimeSec%60);
fflush(stdout);
if (TheGlobalData->m_exportStats)
ExportGameStatsJSON(TheRecorder->getReplayDir(), filename);
}
else
{
Expand Down Expand Up @@ -171,11 +179,20 @@ int ReplaySimulation::simulateReplaysInWorkerProcesses(const std::vector<AsciiSt
UnicodeString filenameWide;
filenameWide.translate(filenames[filenamePositionStarted]);
UnicodeString command;
command.format(L"\"%s\"%s%s -replay \"%s\"",
command.format(L"\"%s\"%s%s%s -replay \"%s\"",
exePath,
TheGlobalData->m_windowed ? L" -win" : L"",
TheGlobalData->m_headless ? L" -headless" : L"",
TheGlobalData->m_exportStats ? L" -exportStats" : L"",
filenameWide.str());
if (!TheGlobalData->m_statsUrl.isEmpty())
{
UnicodeString statsUrlWide;
statsUrlWide.translate(TheGlobalData->m_statsUrl);
command.concat(L" -statsUrl \"");
command.concat(statsUrlWide);
command.concat(L"\"");
}

processes.push_back(WorkerProcess());
processes.back().startProcess(command);
Expand Down
4 changes: 4 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ set(GAMEENGINE_SRC
Include/Common/StackDump.h
Include/Common/StateMachine.h
Include/Common/StatsCollector.h
Include/Common/StatsExporter.h
Include/Common/StatsUploader.h
# Include/Common/STLTypedefs.h
# Include/Common/StreamingArchiveFile.h
# Include/Common/SubsystemInterface.h
Expand Down Expand Up @@ -630,6 +632,8 @@ set(GAMEENGINE_SRC
Source/Common/SkirmishBattleHonors.cpp
Source/Common/StateMachine.cpp
Source/Common/StatsCollector.cpp
Source/Common/StatsExporter.cpp
Source/Common/StatsUploader.cpp
# Source/Common/System/ArchiveFile.cpp
# Source/Common/System/ArchiveFileSystem.cpp
# Source/Common/System/AsciiString.cpp
Expand Down
19 changes: 19 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/AcademyStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@ class AcademyStats : public Snapshot

Bool calculateAcademyAdvice( AcademyAdviceInfo *info );

UnsignedInt getSupplyCentersBuilt() const { return m_supplyCentersBuilt; }
UnsignedInt getPeonsBuilt() const { return m_peonsBuilt; }
UnsignedInt getStructuresCaptured() const { return m_structuresCaptured; }
UnsignedInt getGeneralsPointsSpent() const { return m_generalsPointsSpent; }
UnsignedInt getSpecialPowersUsed() const { return m_specialPowersUsed; }
UnsignedInt getStructuresGarrisoned() const { return m_structuresGarrisoned; }
UnsignedInt getUpgradesPurchased() const { return m_upgradesPurchased; }
UnsignedInt getGatherersBuilt() const { return m_gatherersBuilt; }
UnsignedInt getHeroesBuilt() const { return m_heroesBuilt; }
UnsignedInt getControlGroupsUsed() const { return m_controlGroupsUsed; }
UnsignedInt getSecondaryIncomeUnitsBuilt() const { return m_secondaryIncomeUnitsBuilt; }
UnsignedInt getClearedGarrisonedBuildings() const { return m_clearedGarrisonedBuildings; }
UnsignedInt getSalvageCollected() const { return m_salvageCollected; }
UnsignedInt getGuardAbilityUsedCount() const { return m_guardAbilityUsedCount; }
UnsignedInt getDoubleClickAttackMoveOrdersGiven() const { return m_doubleClickAttackMoveOrdersGiven; }
UnsignedInt getMinesCleared() const { return m_minesCleared; }
UnsignedInt getVehiclesDisguised() const { return m_vehiclesDisguised; }
UnsignedInt getFirestormsCreated() const { return m_firestormsCreated; }

protected:
// snapshot methods
virtual void crc( Xfer *xfer );
Expand Down
6 changes: 6 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ class GlobalData : public SubsystemInterface
// Run game without graphics, input or audio.
Bool m_headless;

// Export game stats as JSON alongside replay file.
Bool m_exportStats;

// URL to POST compressed stats JSON after export.
AsciiString m_statsUrl;

Bool m_windowed;
Int m_xResolution;
Int m_yResolution;
Expand Down
44 changes: 44 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/StatsExporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
** Command & Conquer Generals Zero Hour(tm)
**
** 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 <http://www.gnu.org/licenses/>.
*/

#pragma once

class AsciiString;
class Object;
class Player;
class DamageInfo;

/// Export game statistics as a JSON file alongside the replay file.
/// @param replayDir Directory containing replays (e.g. "[UserDataPath]/Replays/")
/// @param replayFileName Replay filename with extension (e.g. "LastReplay.rep")
void ExportGameStatsJSON(const AsciiString& replayDir, const AsciiString& replayFileName);

/// Collect a time-series snapshot of all players' stats (called every game logic frame).
/// Snapshots are taken every 30 frames (~1 second) and stored in memory.
void StatsExporterCollectSnapshot();

/// Begin recording stats for a new replay. Activates recording and resets all stored data.
void StatsExporterBeginRecording();

/// Record a kill event with full context (called from Object::scoreTheKill).
void StatsExporterRecordKill(const Object *killer, const Object *victim, const DamageInfo *damageInfo);

/// Record a build event (called from Player::onUnitCreated / onStructureConstructionComplete).
void StatsExporterRecordBuild(const Object *producer, const Object *built);

/// Record a capture event (called from Object::onCapture).
void StatsExporterRecordCapture(const Object *captured, const Player *oldOwner, const Player *newOwner);
27 changes: 27 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/StatsUploader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
** Command & Conquer Generals Zero Hour(tm)
**
** 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 <http://www.gnu.org/licenses/>.
*/

#pragma once

class AsciiString;

/// Upload gzip-compressed stats data to a REST endpoint via HTTP POST.
/// @param url Full URL including path (e.g. "http://server:8080/stats")
/// @param data Pointer to gzip-compressed data
/// @param dataLen Length of compressed data in bytes
/// @param seed Game seed for the X-Game-Seed header
void UploadStatsToServer(const AsciiString& url, const void *data, unsigned int dataLen, unsigned int seed);
22 changes: 22 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,22 @@ Int parseHeadless(char *args[], int num)
return 1;
}

Int parseExportStats(char *args[], int num)
{
TheWritableGlobalData->m_exportStats = TRUE;
return 1;
}

Int parseStatsUrl(char *args[], int num)
{
if (num > 1)
{
TheWritableGlobalData->m_statsUrl = args[1];
return 2;
}
return 1;
}

Int parseReplay(char *args[], int num)
{
if (num > 1)
Expand Down Expand Up @@ -1175,6 +1191,12 @@ static CommandLineParam paramsForStartup[] =
// (If you have 4 cores, call it with -jobs 4)
// If you do not call this, all replays will be simulated in sequence in the same process.
{ "-jobs", parseJobs },

// Export game stats as JSON alongside replay file.
{ "-exportStats", parseExportStats },

// URL to POST compressed stats JSON after export.
{ "-statsUrl", parseStatsUrl },
};

// These Params are parsed during Engine Init before INI data is loaded
Expand Down
8 changes: 7 additions & 1 deletion GeneralsMD/Code/GameEngine/Source/Common/GameMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ Int GameMain()
TheGameEngine = CreateGameEngine();
TheGameEngine->init();

if (!TheGlobalData->m_simulateReplays.empty())
if (TheGlobalData->m_exportStats && (!TheGlobalData->m_headless || TheGlobalData->m_simulateReplays.empty()))
{
printf("ERROR: -exportStats requires headless replay mode (-headless -replay <file>).\n");
fflush(stdout);
exitcode = 1;
}
else if (!TheGlobalData->m_simulateReplays.empty())
{
exitcode = ReplaySimulation::simulateReplays(TheGlobalData->m_simulateReplays, TheGlobalData->m_simulateReplayJobs);
}
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ GlobalData::GlobalData()
m_framesPerSecondLimit = 0;
m_chipSetType = 0;
m_headless = FALSE;
m_exportStats = FALSE;
m_windowed = 0;
m_xResolution = 800;
m_yResolution = 600;
Expand Down
7 changes: 7 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

#include "Common/ActionManager.h"
#include "Common/BuildAssistant.h"
#include "Common/StatsExporter.h"
#include "Common/CRCDebug.h"
#include "Common/DisabledTypes.h"
#include "Common/GameState.h"
Expand Down Expand Up @@ -1556,6 +1557,10 @@ void Player::onUnitCreated( Object *factory, Object *unit )

// increment our scorekeeper
m_scoreKeeper.addObjectBuilt(unit);
m_scoreKeeper.addMoneySpent(unit->getTemplate()->calcCostToBuild(this));

if (TheGlobalData->m_exportStats)
StatsExporterRecordBuild(factory, unit);

// ai notification callback
if( m_ai )
Expand Down Expand Up @@ -1645,6 +1650,8 @@ void Player::onStructureConstructionComplete( Object *builder, Object *structure
if (isRebuild == FALSE) {
m_scoreKeeper.addObjectBuilt(structure);
m_scoreKeeper.addMoneySpent(structure->getTemplate()->calcCostToBuild(this));
if (TheGlobalData->m_exportStats)
StatsExporterRecordBuild(builder, structure);
}

structure->friend_adjustPowerForPlayer(TRUE);
Expand Down
Loading
Loading