Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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: 1 addition & 1 deletion YRpp
1 change: 1 addition & 0 deletions src/Spawner/Spawner.Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ void SpawnerConfig::LoadFromINIFile(CCINIClass* pINI)
QuickMatch = pINI->ReadBool(pSettingsSection, "QuickMatch", QuickMatch);
SkipScoreScreen = pINI->ReadBool(pSettingsSection, "SkipScoreScreen", SkipScoreScreen);
WriteStatistics = pINI->ReadBool(pSettingsSection, "WriteStatistics", WriteStatistics);
GenerateStatistics = pINI->ReadBool(pSettingsSection, "GenerateStatistics", GenerateStatistics);
AINamesByDifficulty = pINI->ReadBool(pSettingsSection, "AINamesByDifficulty", AINamesByDifficulty);
ContinueWithoutHumans = pINI->ReadBool(pSettingsSection, "ContinueWithoutHumans", ContinueWithoutHumans);
DefeatedBecomesObserver = pINI->ReadBool(pSettingsSection, "DefeatedBecomesObserver", DefeatedBecomesObserver);
Expand Down
2 changes: 2 additions & 0 deletions src/Spawner/Spawner.Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class SpawnerConfig
bool QuickMatch;
bool SkipScoreScreen;
bool WriteStatistics;
bool GenerateStatistics;
bool AINamesByDifficulty;
bool ContinueWithoutHumans;
bool DefeatedBecomesObserver;
Expand Down Expand Up @@ -241,6 +242,7 @@ class SpawnerConfig
, QuickMatch { false }
, SkipScoreScreen { Main::GetConfig()->SkipScoreScreen }
, WriteStatistics { false }
, GenerateStatistics { true }
, AINamesByDifficulty { false }
, ContinueWithoutHumans { false }
, DefeatedBecomesObserver { false }
Expand Down
77 changes: 77 additions & 0 deletions src/Spawner/Statistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "Spawner.h"

#include <CCFileClass.h>
#include <FPSCounter.h>
#include <HouseClass.h>
#include <PacketClass.h>
#include <ScenarioClass.h>
Expand All @@ -28,13 +29,80 @@
#include <Utilities/Debug.h>
#include <Utilities/Macro.h>


// MP score global statistics array, populated by the original sub_5C98A0
// during MPScore_InitDialog. The in-game score screen reads from the same
// array, so reading it here guarantees DTA.LOG matches the in-game display
// exactly (including Score's if(>0) filter and the winner bonus).
// Each record is 112 bytes with 16-byte field spacing (Westwood-style align).
struct MPScoreStatsEntry
{
wchar_t Name[20]; // 0x00 Player name (copy of pHouse->UIName)
int Scheme; // 0x28 Scenario index
int Winner; // 0x2C 1=winner 0=loser
int Lost; // 0x30 Lost = TotalKilledUnits + TotalKilledBuildings
int _pad0[3]; // 0x34
int Kills; // 0x40 Kills = sum(KilledUnitsOfHouses) + sum(KilledBuildingsOfHouses)
int _pad1[3]; // 0x44
int Built; // 0x50 Built = sum of 4 FactoryProduced*Types.GetTotal()
int _pad2[3]; // 0x54
int Score; // 0x60 Score (with if(>0) filter and winner bonus applied)
int _pad3[3]; // 0x64
};
Comment thread
GreyVacuum marked this conversation as resolved.
Outdated
static_assert(sizeof(MPScoreStatsEntry) == 112, "MPScoreStatsEntry size mismatch");

DEFINE_POINTER(MPScoreStatsEntry, MPScoreStatsArray, 0xA8D1FCu)
DEFINE_POINTER(int, MPScoreStatsCount, 0xA8D580u)
Comment thread
GreyVacuum marked this conversation as resolved.
Outdated

bool __forceinline IsStatisticsEnabled()
{
return Spawner::Active
&& Spawner::GetConfig()->WriteStatistics
&& !SessionClass::IsCampaign();
}

static void WriteDTALog()
{
if (!Spawner::Active)
return;

if (!Spawner::GetConfig()->GenerateStatistics)
return;

CreateDirectoryA("debug", nullptr);

CCFileClass file = CCFileClass("debug\\Statistic.log");
if (!file.Open(FileAccessMode::Write))
return;

// Read from the global array populated by sub_5C98A0, the same data
// source as the in-game score screen. The array is filled after
// MPScore_InitDialog (0x5C9D42) calls sub_5C98A0.
const int count = *MPScoreStatsCount;

for (int i = 0; i < count; i++)
{
const auto& entry = MPScoreStatsArray[i];

char name[64] = { 0 };
WideCharToMultiByte(CP_UTF8, 0, entry.Name, -1, name, sizeof(name), nullptr, nullptr);

const char* result = entry.Winner ? "Winner" : "Loser";

char buffer[256] = { 0 };
sprintf_s(buffer, "%s: %s\n Lost = %d\n Kills = %d\n Built = %d\n Score = %d\n",
name, result, entry.Lost, entry.Kills, entry.Built, entry.Score);
file.WriteBytes(buffer, static_cast<int>(strlen(buffer)));
}

char fpsBuffer[128] = { 0 };
sprintf_s(fpsBuffer, "Game loop finished. Average FPS = %d\n",
static_cast<int>(FPSCounter::GetAverageFrameRate()));
file.WriteBytes(fpsBuffer, static_cast<int>(strlen(fpsBuffer)));

file.Close();
}

// Write stats.dmp
DEFINE_HOOK(0x6C856C, SendStatisticsPacket_WriteStatisticsDump, 0x5)
{
Expand All @@ -59,6 +127,15 @@ DEFINE_HOOK(0x6C856C, SendStatisticsPacket_WriteStatisticsDump, 0x5)
return 0;
}

DEFINE_HOOK(0x5C9D47, MPScore_InitDialog_WriteDTALog, 0x6)
{
// sub_5C98A0 has just finished (called at 0x5C9D42), so the global
// statistics array is now populated with the exact same Score shown
// in-game (including the if(>0) filter and the winner bonus).
WriteDTALog();
return 0;
}

// Send AI player
// Dont send observer
DEFINE_HOOK(0x6C73F8, SendStatisticsPacket_HouseFilter, 0x6)
Expand Down