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: 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
83 changes: 83 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,86 @@
#include <Utilities/Debug.h>
#include <Utilities/Macro.h>


// MAX_MULTI_GAMES index used by the single-result MP score screen.
// The array supports multi-game (tournament) accumulation, but in RA2/YR's
// standard non-tournament MPScore dialog sub_5C98A0 only writes slot 0.
static constexpr int ScoreGameSlot = 0;

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 SessionClass::MPScores 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 = SessionClass::MPScoreCount;

// Walk HouseClass::Array in the same order as sub_5C98A0 to map each
// MPScores entry back to its HouseClass, so we can tell human players
// from AI players. AI players are always named "Computer" regardless of
// the localised UIName the game copies into the score array.
int scoreIndex = 0;

for (auto pHouse : HouseClass::Array)
{
// Same filter as sub_5C98A0: skip null, passive, and observer houses.
if (!pHouse
|| (pHouse->Type && pHouse->Type->MultiplayPassive)
|| pHouse->IsObserver())
continue;

if (scoreIndex >= count)
break;

const auto& entry = SessionClass::MPScores[scoreIndex];

const wchar_t* display_name = pHouse->IsHumanPlayer
? entry.Name : L"Computer";

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

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

const int lost = entry.Lost[ScoreGameSlot];
const int kills = entry.Kills[ScoreGameSlot];
const int built = entry.Built[ScoreGameSlot];
const int score = entry.Score[ScoreGameSlot];

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

++scoreIndex;
}

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 +133,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