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
2 changes: 2 additions & 0 deletions codxe.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
<ClCompile Include="src\game\t4\mp\components\image_loader.cpp" />
<ClCompile Include="src\game\t4\mp\components\map.cpp" />
<ClCompile Include="src\game\t4\mp\components\patches.cpp" />
<ClCompile Include="src\game\t4\mp\components\stats.cpp" />
<ClCompile Include="src\game\t4\mp\components\sv_bots.cpp" />
<ClCompile Include="src\game\t4\mp\components\ui.cpp" />

Expand Down Expand Up @@ -284,6 +285,7 @@
<ClInclude Include="src\game\t4\mp\components\image_loader.h" />
<ClInclude Include="src\game\t4\mp\components\map.h" />
<ClInclude Include="src\game\t4\mp\components\patches.h" />
<ClInclude Include="src\game\t4\mp\components\stats.h" />
<ClInclude Include="src\game\t4\mp\components\sv_bots.h" />
<ClInclude Include="src\game\t4\mp\components\ui.h" />

Expand Down
25 changes: 25 additions & 0 deletions src/game/t4/mp/components/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace mp
{

std::vector<std::function<void()>> Events::dvarinit_callbacks;
std::vector<std::function<void()>> Events::cmdinit_callbacks;
std::vector<std::function<void()>> Events::vmshutdown_callbacks;
std::vector<std::function<void()>> Events::ui_refresh_callbacks;

Expand All @@ -29,6 +30,25 @@ void Events::OnDvarInit(const std::function<void()> &callback)

Detour Events::Com_InitDvars_Detour;

void Events::Cmd_Init_Hook()
{
Cmd_Init_Detour.GetOriginal<Cmd_Init_t>()();

for (auto it = cmdinit_callbacks.begin(); it != cmdinit_callbacks.end(); ++it)
{
(*it)();
}

cmdinit_callbacks.clear();
}

void Events::OnCmdInit(const std::function<void()> &callback)
{
cmdinit_callbacks.emplace_back(callback);
}

Detour Events::Cmd_Init_Detour;

void *Events::Scr_ShutdownSystem_Hook(scriptInstance_t inst, int sys, int bComplete)
{
for (auto it = vmshutdown_callbacks.begin(); it != vmshutdown_callbacks.end(); ++it)
Expand Down Expand Up @@ -70,6 +90,9 @@ Events::Events()
Com_InitDvars_Detour = Detour(Com_InitDvars, Com_InitDvars_Hook);
Com_InitDvars_Detour.Install();

Cmd_Init_Detour = Detour(Cmd_Init, Cmd_Init_Hook);
Cmd_Init_Detour.Install();

Scr_ShutdownSystem_Detour = Detour(Scr_ShutdownSystem, Scr_ShutdownSystem_Hook);
Scr_ShutdownSystem_Detour.Install();

Expand All @@ -80,10 +103,12 @@ Events::Events()
Events::~Events()
{
Com_InitDvars_Detour.Remove();
Cmd_Init_Detour.Remove();
Scr_ShutdownSystem_Detour.Remove();
UI_Refresh_Detour.Remove();

dvarinit_callbacks.clear();
cmdinit_callbacks.clear();
vmshutdown_callbacks.clear();
ui_refresh_callbacks.clear();
}
Expand Down
5 changes: 5 additions & 0 deletions src/game/t4/mp/components/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Events : public Module
};

static void OnDvarInit(const std::function<void()> &callback);
static void OnCmdInit(const std::function<void()> &callback);
static void OnVMShutdown(const std::function<void()> &callback);
static void OnUIRefresh(const std::function<void()> &callback);

Expand All @@ -26,6 +27,10 @@ class Events : public Module
static Detour Com_InitDvars_Detour;
static void Com_InitDvars_Hook();

static std::vector<std::function<void()>> cmdinit_callbacks;
static Detour Cmd_Init_Detour;
static void Cmd_Init_Hook();

static std::vector<std::function<void()>> vmshutdown_callbacks;
static Detour Scr_ShutdownSystem_Detour;
static void *Scr_ShutdownSystem_Hook(scriptInstance_t inst, int sys, int bComplete);
Expand Down
240 changes: 240 additions & 0 deletions src/game/t4/mp/components/stats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
#include "pch.h"
#include <cctype>
#include <stdlib.h>
#include "events.h"
#include "stats.h"

namespace t4
{
namespace mp
{
namespace
{
cmd_function_s Cmd_UnlockStats_VAR;

const char *TableLookup(const StringTable *table, int row, int column)
{
if (!table || row < 0 || column < 0 || row >= table->rowCount || column >= table->columnCount || !table->values)
{
return "";
}

const char *value = table->values[row * table->columnCount + column];
return value ? value : "";
}

bool TryParseInt(const char *text, int *out)
{
if (!text || !*text)
{
return false;
}

const char *p = text;
if (*p == '-')
{
++p;
}

if (*p < '0' || *p > '9')
{
return false;
}

*out = atoi(text);
return true;
}

bool HasText(const char *text)
{
return text && *text;
}

std::string ToLower(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(),
[](char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); });
return value;
}

const StringTable *FindStringTable(const char *name)
{
StringTable *table = nullptr;
if (StringTable_GetAsset(name, &table) && table)
{
return table;
}

const std::string lowerName = ToLower(name);
if (StringTable_GetAsset(lowerName.c_str(), &table) && table)
{
return table;
}

return nullptr;
}

void ExecuteCommand(const char *command)
{
char commandLine[1024] = {};
const size_t commandLength = std::strlen(command);

if (commandLength >= sizeof(commandLine) - 1)
{
Com_Printf(0, "unlockstats: command too long: %s\n", command);
return;
}

std::memcpy(commandLine, command, commandLength);
commandLine[commandLength] = '\n';

Cbuf_ExecuteBuffer(0, 0, commandLine);
}

void StatSet(int stat, int value)
{
ExecuteCommand(va(const_cast<char *>("statset %i %i"), stat, value));
}

void UnlockRank()
{
const StringTable *rankTable = FindStringTable("mp/ranktable.csv");
int maxRank = 64;
int minXp = 148680;
int maxXp = 153950;

if (rankTable)
{
for (int row = 1; row < rankTable->rowCount; ++row)
{
if (std::strcmp(TableLookup(rankTable, row, 0), "maxrank") == 0)
{
TryParseInt(TableLookup(rankTable, row, 1), &maxRank);
continue;
}

int rank = 0;
int xp = 0;
if (TryParseInt(TableLookup(rankTable, row, 0), &rank) &&
TryParseInt(TableLookup(rankTable, row, 7), &xp) && xp >= maxXp)
{
maxRank = rank;
TryParseInt(TableLookup(rankTable, row, 2), &minXp);
maxXp = xp;
}
}
}

StatSet(2301, maxXp);
StatSet(2326, 10);
StatSet(2350, maxRank);
StatSet(2351, minXp);
StatSet(2352, maxXp);
StatSet(2353, maxXp);
StatSet(251, maxRank);
StatSet(252, maxRank);
}

void FlushChallengeGroup(int stateStat, int progressStat, int maxProgress, int &challengeCount)
{
if (stateStat <= 0 || progressStat <= 0)
{
return;
}

// GSC treats 255 as a completed challenge state; 1..n are active tiers.
StatSet(stateStat, 255);
StatSet(progressStat, maxProgress);
++challengeCount;
}

void UnlockChallengeTable(const StringTable *challengeTable, int &challengeCount)
{
int stateStat = 0;
int progressStat = 0;
int maxProgress = 0;

for (int row = 1; row < challengeTable->rowCount; ++row)
{
int newStateStat = 0;
if (TryParseInt(TableLookup(challengeTable, row, 2), &newStateStat))
{
FlushChallengeGroup(stateStat, progressStat, maxProgress, challengeCount);

stateStat = newStateStat;
TryParseInt(TableLookup(challengeTable, row, 3), &progressStat);
maxProgress = 0;
}

if (stateStat <= 0)
{
continue;
}

int target = 0;
if (TryParseInt(TableLookup(challengeTable, row, 4), &target) && target > maxProgress)
{
maxProgress = target;
}
}

FlushChallengeGroup(stateStat, progressStat, maxProgress, challengeCount);
}

void UnlockChallenges(int &challengeCount)
{
const StringTable *challengeList = FindStringTable("mp/challengetable.csv");
if (!challengeList)
{
Com_Printf(0, "unlockstats: mp/challengetable.csv not found\n");
return;
}

for (int row = 1; row < challengeList->rowCount; ++row)
{
const char *challengeTableName = TableLookup(challengeList, row, 4);
if (!HasText(challengeTableName))
{
continue;
}

const StringTable *challengeTable = FindStringTable(challengeTableName);
if (!challengeTable)
{
Com_Printf(0, "unlockstats: %s not found\n", challengeTableName);
continue;
}

UnlockChallengeTable(challengeTable, challengeCount);
}
}

void Cmd_UnlockStats_f()
{
int challengeCount = 0;

ExecuteCommand("exec mp/unlock_init.cfg");
UnlockRank();
UnlockChallenges(challengeCount);
ExecuteCommand("updategamerprofile");

Com_Printf(0, "unlockstats: queued stats and %i challenge groups\n", challengeCount);
}

void RegisterCommands()
{
Cmd_AddCommandInternal("unlockstats", Cmd_UnlockStats_f, &Cmd_UnlockStats_VAR);
}
} // namespace

stats::stats()
{
Events::OnCmdInit(RegisterCommands);
}

stats::~stats()
{
}

} // namespace mp
} // namespace t4
21 changes: 21 additions & 0 deletions src/game/t4/mp/components/stats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include "pch.h"

namespace t4
{
namespace mp
{
class stats : public Module
{
public:
stats();
~stats();

const char *get_name() override
{
return "stats";
}
};
} // namespace mp
} // namespace t4
2 changes: 2 additions & 0 deletions src/game/t4/mp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "components/image_loader.h"
#include "components/map.h"
#include "components/patches.h"
#include "components/stats.h"
#include "components/sv_bots.h"
#include "components/ui.h"

Expand All @@ -34,6 +35,7 @@ T4_MP_Plugin::T4_MP_Plugin()
// RegisterModule(new ImageLoader());
RegisterModule(new Map());
RegisterModule(new Patches());
RegisterModule(new stats());
RegisterModule(new ui());
}

Expand Down
Loading
Loading