Feature/set get stat (#485)

* [Script]: GetStat & SetStat from CoD4(x)

* [Stats] Remove limitation

* [Bullet] Clean this up
This commit is contained in:
Edo
2022-09-13 20:39:45 +02:00
committed by GitHub
parent be1bc5fd10
commit e49194ff65
12 changed files with 185 additions and 43 deletions

View File

@ -20,7 +20,7 @@ namespace Components
}
// Game's code
if (surfaceType != Game::materialSurfType_t::SURF_TYPE_DEFAULT)
if (surfaceType != Game::SURF_TYPE_DEFAULT)
{
return (*Game::penetrationDepthTable)[weapDef->penetrateType][surfaceType];
}

View File

@ -272,18 +272,5 @@ namespace Components
// clanName in CG_Obituary
Utils::Hook(0x586DD6, PlayerName::GetClientName, HOOK_CALL).install()->quick();
Utils::Hook(0x586E2A, PlayerName::GetClientName, HOOK_CALL).install()->quick();
Command::Add("statGet", [](Command::Params* params)
{
if (params->size() < 2)
{
Logger::PrintError(Game::CON_CHANNEL_SERVER, "statget usage: statget <index>\n");
return;
}
const auto index = std::atoi(params->get(1));
const auto stat = Game::LiveStorage_GetStat(0, index);
Logger::Print(Game::CON_CHANNEL_SYSTEM, "Stat {}: {}\n", index, stat);
});
}
}

View File

@ -246,7 +246,7 @@ namespace Components
void ScriptExtension::AddMethods()
{
// ScriptExtension methods
Script::AddMethod("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp()
Script::AddMethod("GetIp", [](const Game::scr_entref_t entref) // gsc: self GetIp()
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
@ -259,7 +259,7 @@ namespace Components
Game::Scr_AddString(ip.data());
});
Script::AddMethod("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing()
Script::AddMethod("GetPing", [](const Game::scr_entref_t entref) // gsc: self GetPing()
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
@ -267,7 +267,7 @@ namespace Components
Game::Scr_AddInt(client->ping);
});
Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing(<int>)
Script::AddMethod("SetPing", [](const Game::scr_entref_t entref) // gsc: self SetPing(<int>)
{
auto ping = Game::Scr_GetInt(0);
@ -306,7 +306,7 @@ namespace Components
void ScriptExtension::AddEntityFields()
{
AddEntityField("entityflags", Game::fieldtype_t::F_INT,
AddEntityField("entityflags", Game::F_INT,
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
{
ent->flags = Game::Scr_GetInt(0);
@ -319,7 +319,7 @@ namespace Components
void ScriptExtension::AddClientFields()
{
AddClientField("clientflags", Game::fieldtype_t::F_INT,
AddClientField("clientflags", Game::F_INT,
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
{
pSelf->flags = Game::Scr_GetInt(0);

View File

@ -2,18 +2,18 @@
namespace Components
{
std::unordered_map<std::int32_t, std::function<bool(Command::Params*)>> ServerCommands::Commands;
std::unordered_map<std::int32_t, ServerCommands::serverCommandHandler> ServerCommands::Commands;
void ServerCommands::OnCommand(std::int32_t cmd, std::function<bool(Command::Params*)> callback)
void ServerCommands::OnCommand(std::int32_t cmd, const serverCommandHandler& callback)
{
ServerCommands::Commands.insert_or_assign(cmd, std::move(callback));
Commands.insert_or_assign(cmd, callback);
}
bool ServerCommands::OnServerCommand()
{
Command::ClientParams params;
for (const auto& [id, callback] : ServerCommands::Commands)
for (const auto& [id, callback] : Commands)
{
if (params.size() >= 1)
{
@ -33,7 +33,10 @@ namespace Components
{
push eax
pushad
// Missing localClientNum!
call ServerCommands::OnServerCommand
mov [esp + 20h], eax
popad
pop eax
@ -63,7 +66,7 @@ namespace Components
ServerCommands::ServerCommands()
{
// Server command receive hook
Utils::Hook(0x59449F, ServerCommands::CG_DeployServerCommand_Stub).install()->quick();
Utils::Hook(0x59449F, CG_DeployServerCommand_Stub).install()->quick();
Utils::Hook::Nop(0x5944A4, 6);
}
}

View File

@ -7,10 +7,11 @@ namespace Components
public:
ServerCommands();
static void OnCommand(std::int32_t cmd, std::function<bool(Command::Params*)> callback);
using serverCommandHandler = std::function<bool(Command::Params*)>;
static void OnCommand(std::int32_t cmd, const serverCommandHandler& callback);
private:
static std::unordered_map<std::int32_t, std::function<bool(Command::Params*)>> Commands;
static std::unordered_map<std::int32_t, serverCommandHandler> Commands;
static bool OnServerCommand();
static void CG_DeployServerCommand_Stub();

View File

@ -1,10 +1,11 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{
int64_t* Stats::GetStatsID()
std::int64_t* Stats::GetStatsID()
{
static int64_t id = 0x110000100001337;
static std::int64_t id = 0x110000100001337;
return &id;
}
@ -58,7 +59,7 @@ namespace Components
void Stats::UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{
Stats::SendStats();
SendStats();
}
int Stats::SaveStats(char* dest, const char* folder, const char* buffer, size_t length)
@ -73,18 +74,64 @@ namespace Components
return Utils::Hook::Call<int(char*, const char*, const char*, size_t)>(0x426450)(dest, folder, buffer, length);
}
void Stats::AddScriptFunctions()
{
Script::AddMethod("GetStat", [](const Game::scr_entref_t entref)
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto index = Game::Scr_GetInt(0);
if (index < 0 || index > 3499)
{
Game::Scr_ParamError(0, Utils::String::VA("GetStat: invalid index %i", index));
}
if (ent->client->sess.connected <= Game::CON_DISCONNECTED)
{
Game::Scr_Error("GetStat: called on a disconnected player");
}
Game::Scr_AddInt(Game::SV_GetClientStat(ent->s.number, index));
});
Script::AddMethod("SetStat", [](const Game::scr_entref_t entref)
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto iNumParms = Game::Scr_GetNumParam();
if (iNumParms != 2)
{
Game::Scr_Error(Utils::String::VA("GetStat: takes 2 arguments, got %u.\n", iNumParms));
}
const auto index = Game::Scr_GetInt(0);
if (index < 0 || index > 3499)
{
Game::Scr_ParamError(0, Utils::String::VA("setstat: invalid index %i", index));
}
const auto value = Game::Scr_GetInt(1);
if (index < 2000 && (value < 0 || value > 255))
{
Game::Scr_ParamError(1, Utils::String::VA("setstat: index %i is a byte value, and you're trying to set it to %i", index, value));
}
Game::SV_SetClientStat(ent->s.number, index, value);
});
}
Stats::Stats()
{
// This UIScript should be added in the onClose code of the cac_popup menu,
// so everytime the create-a-class window is closed, and a client is connected
// to a server, the stats data of the client will be reuploaded to the server.
// allowing the player to change their classes while connected to a server.
UIScript::Add("UpdateClasses", Stats::UpdateClasses);
UIScript::Add("UpdateClasses", UpdateClasses);
// Allow playerdata to be changed while connected to a server
Utils::Hook::Set<BYTE>(0x4376FD, 0xEB);
// ToDo: Allow playerdata changes in setPlayerData UI script.
// TODO: Allow playerdata changes in setPlayerData UI script.
// Rename stat file
Utils::Hook::SetString(0x71C048, "iw4x.stat");
@ -92,8 +139,9 @@ namespace Components
// Patch stats steamid
Utils::Hook::Nop(0x682EBF, 20);
Utils::Hook::Nop(0x6830B1, 20);
Utils::Hook(0x682EBF, Stats::GetStatsID, HOOK_CALL).install()->quick();
Utils::Hook(0x6830B1, Stats::GetStatsID, HOOK_CALL).install()->quick();
Utils::Hook(0x682EBF, GetStatsID, HOOK_CALL).install()->quick();
Utils::Hook(0x6830B1, GetStatsID, HOOK_CALL).install()->quick();
//Utils::Hook::Set<BYTE>(0x68323A, 0xEB);
// Never use remote stat saving
@ -103,6 +151,34 @@ namespace Components
Utils::Hook::Nop(0x402CE6, 2);
// Write stats to mod folder if a mod is loaded
Utils::Hook(0x682F7B, Stats::SaveStats, HOOK_CALL).install()->quick();
Utils::Hook(0x682F7B, SaveStats, HOOK_CALL).install()->quick();
AddScriptFunctions();
// Skip silly Com_Error (LiveStorage_SetStat)
Utils::Hook::Set<BYTE>(0x4CC5F9, 0xEB);
// 'M' Seems to be used on Xbox only for parsing platform specific ranks
ServerCommands::OnCommand('M', [](Command::Params* params)
{
const auto* arg1 = params->get(1);
const auto* arg2 = params->get(2);
Game::LiveStorage_SetStat(Game::CL_ControllerIndexFromClientNum(0), std::atoi(arg1), std::atoi(arg2));
return true;
});
Command::Add("statGet", []([[maybe_unused]] Command::Params* params)
{
if (params->size() < 2)
{
Logger::PrintError(Game::CON_CHANNEL_SERVER, "statget usage: statget <index>\n");
return;
}
const auto index = std::atoi(params->get(1));
const auto stat = Game::LiveStorage_GetStat(0, index);
Logger::Print(Game::CON_CHANNEL_SYSTEM, "Stat {}: {}\n", index, stat);
});
}
}

View File

@ -11,9 +11,12 @@ namespace Components
private:
static void UpdateClasses([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info);
static void SendStats();
static int SaveStats(char* dest, const char* folder, const char* buffer, size_t length);
static int64_t* GetStatsID();
static std::int64_t* GetStatsID();
static void AddScriptFunctions();
};
}