Skip to content
Open
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
2 changes: 2 additions & 0 deletions Generals/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ set(GAMEENGINE_SRC
Include/Common/StackDump.h
Include/Common/StateMachine.h
Include/Common/StatsCollector.h
Include/Common/StatsExporter.h
# Include/Common/STLTypedefs.h
# Include/Common/StreamingArchiveFile.h
# Include/Common/SubsystemInterface.h
Expand Down Expand Up @@ -586,6 +587,7 @@ set(GAMEENGINE_SRC
Source/Common/SkirmishBattleHonors.cpp
Source/Common/StateMachine.cpp
Source/Common/StatsCollector.cpp
Source/Common/StatsExporter.cpp
# Source/Common/System/ArchiveFile.cpp
# Source/Common/System/ArchiveFileSystem.cpp
# Source/Common/System/AsciiString.cpp
Expand Down
4 changes: 4 additions & 0 deletions Generals/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ class GlobalData : public SubsystemInterface
// Run game without graphics, input or audio.
Bool m_headless;

// TheSuperHackers @feature bill-rich 11/03/2026
// Export game stats as JSON alongside replay file.
Bool m_exportStats;

Bool m_windowed;
Int m_xResolution;
Int m_yResolution;
Expand Down
10 changes: 9 additions & 1 deletion Generals/Code/GameEngine/Include/Common/ScoreKeeper.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ class ScoreKeeper : public Snapshot
// for battle honor calculation. done once at the end of each online game
Int getTotalUnitsBuilt( KindOfMaskType validMask, KindOfMaskType invalidMask );

// TheSuperHackers @feature bill-rich 10/03/2026 Public accessors for game stats export.
typedef std::map<const ThingTemplate *, Int> ObjectCountMap;
Int getUnitsDestroyedByPlayer( Int idx ) const { return m_totalUnitsDestroyed[idx]; }
Int getBuildingsDestroyedByPlayer( Int idx ) const { return m_totalBuildingsDestroyed[idx]; }
const ObjectCountMap& getObjectsBuilt() const { return m_objectsBuilt; }
const ObjectCountMap* getObjectsDestroyedArray() const { return m_objectsDestroyed; }
const ObjectCountMap& getObjectsLost() const { return m_objectsLost; }
const ObjectCountMap& getObjectsCaptured() const { return m_objectsCaptured; }

protected:

// snapshot methods
Expand All @@ -119,7 +128,6 @@ class ScoreKeeper : public Snapshot

Int m_myPlayerIdx; ///< We need to not score kills on ourselves... so we need to know who we are

typedef std::map<const ThingTemplate *, Int> ObjectCountMap;
typedef ObjectCountMap::iterator ObjectCountMapIt;
ObjectCountMap m_objectsBuilt; ///< How many and what kinds of objects did we build
ObjectCountMap m_objectsDestroyed[MAX_PLAYER_COUNT]; ///< How many and what kinds and who's did we kill
Expand Down
35 changes: 35 additions & 0 deletions Generals/Code/GameEngine/Include/Common/StatsExporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
** Command & Conquer Generals(tm)
** Copyright 2026 TheSuperHackers
**
** 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/>.
*/

// TheSuperHackers @feature bill-rich 10/03/2026 Game stats JSON exporter.

#pragma once

class AsciiString;

/// 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();

/// Clear all stored time-series snapshots (called at game start/reset).
void StatsExporterClearSnapshots();
11 changes: 11 additions & 0 deletions Generals/Code/GameEngine/Source/Common/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,13 @@ Int parseHeadless(char *args[], int num)
return 1;
}

// TheSuperHackers @feature bill-rich 11/03/2026
Int parseExportStats(char *args[], int num)
{
TheWritableGlobalData->m_exportStats = TRUE;
return 1;
}

Int parseReplay(char *args[], int num)
{
if (num > 1)
Expand Down Expand Up @@ -1148,6 +1155,10 @@ 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 },

// TheSuperHackers @feature bill-rich 11/03/2026
// Export game stats as JSON alongside replay file.
{ "-exportStats", parseExportStats },
};

// These Params are parsed during Engine Init before INI data is loaded
Expand Down
1 change: 1 addition & 0 deletions Generals/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ GlobalData::GlobalData()
m_framesPerSecondLimit = 0;
m_chipSetType = 0;
m_headless = FALSE;
m_exportStats = FALSE;
m_windowed = 0;
m_xResolution = DEFAULT_DISPLAY_WIDTH;
m_yResolution = DEFAULT_DISPLAY_HEIGHT;
Expand Down
5 changes: 5 additions & 0 deletions Generals/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
#include "Common/CRCDebug.h"
#include "Common/OptionPreferences.h"
#include "Common/version.h"
// TheSuperHackers @feature bill-rich 10/03/2026 Export game stats as JSON alongside replay file.
#include "Common/StatsExporter.h"

constexpr const char s_genrep[] = "GENREP";
constexpr const UnsignedInt replayBufferBytes = 8192;
Expand Down Expand Up @@ -731,6 +733,9 @@ void RecorderClass::stopRecording() {
if (m_archiveReplays)
archiveReplay(m_fileName);
}
// TheSuperHackers @feature bill-rich 10/03/2026 Export game stats as JSON alongside replay file.
if (TheGlobalData->m_exportStats && !m_fileName.isEmpty())
ExportGameStatsJSON(getReplayDir(), m_fileName);
m_fileName.clear();
}

Expand Down
Loading