Merge pull request #587 from diamante0018/refactor/script

[Script]: Add IsSprinting & more
This commit is contained in:
Edo 2022-11-24 16:14:13 +00:00 committed by GitHub
commit 82a039e3b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 157 additions and 103 deletions

View File

@ -111,9 +111,15 @@ namespace Components
}
}
void Bots::GScr_isTestClient(Game::scr_entref_t entref)
void Bots::GScr_isTestClient(const Game::scr_entref_t entref)
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* ent = Game::GetEntity(entref);
if (!ent->client)
{
Game::Scr_Error("isTestClient: entity must be a player entity");
return;
}
Game::Scr_AddBool(Game::SV_IsTestClient(ent->s.number) != 0);
}

View File

@ -15,8 +15,8 @@ namespace Components
std::unordered_map<const char*, const char*> Script::ReplacedFunctions;
const char* Script::ReplacedPos = nullptr;
std::vector<int> Script::ScriptMainHandles;
std::vector<int> Script::ScriptInitHandles;
std::unordered_map<std::string, int> Script::ScriptMainHandles;
std::unordered_map<std::string, int> Script::ScriptInitHandles;
void Script::ShowDeprecationWarning()
{
@ -28,16 +28,16 @@ namespace Components
void Script::FunctionError()
{
const auto* funcName = Game::SL_ConvertToString(Script::FunctionName);
const auto* funcName = Game::SL_ConvertToString(FunctionName);
Game::Scr_ShutdownAllocNode();
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, Script::ScriptName);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: unknown function {} in {}\n", funcName, ScriptName);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n");
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, Script::ScriptName);
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function {}\n{}\n\n", funcName, ScriptName);
}
__declspec(naked) void Script::StoreFunctionNameStub()
@ -45,7 +45,7 @@ namespace Components
__asm
{
mov eax, [esp - 8h]
mov Script::FunctionName, ax
mov FunctionName, ax
sub esp, 0Ch
push 0
@ -87,12 +87,12 @@ namespace Components
void Script::StoreScriptName(const char* name)
{
Script::ScriptNameStack.push_back(Script::ScriptName);
Script::ScriptName = name;
ScriptNameStack.push_back(ScriptName);
ScriptName = name;
if (!Utils::String::EndsWith(Script::ScriptName, ".gsc"))
if (!Utils::String::EndsWith(ScriptName, ".gsc"))
{
Script::ScriptName.append(".gsc");
ScriptName.append(".gsc");
}
}
@ -105,7 +105,7 @@ namespace Components
lea ecx, [esp + 30h]
push ecx
call Script::StoreScriptName
call StoreScriptName
add esp, 4h
popad
@ -120,8 +120,8 @@ namespace Components
void Script::RestoreScriptName()
{
Script::ScriptName = Script::ScriptNameStack.back();
Script::ScriptNameStack.pop_back();
ScriptName = ScriptNameStack.back();
ScriptNameStack.pop_back();
}
__declspec(naked) void Script::RestoreScriptNameStub()
@ -129,7 +129,7 @@ namespace Components
__asm
{
pushad
call Script::RestoreScriptName
call RestoreScriptName
popad
mov ds:1CDEAA8h, ebp
@ -205,17 +205,19 @@ namespace Components
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "******* script compile error *******\n");
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "Error: {} ", msgbuf);
Script::PrintSourcePos(Script::ScriptName.data(), offset);
PrintSourcePos(ScriptName.data(), offset);
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "************************************\n\n");
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n{}\n{}\n(see console for actual details)\n", msgbuf, Script::ScriptName);
Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\n{}\n{}\n(see console for actual details)\n", msgbuf, ScriptName);
}
void Script::Scr_LoadGameType_Stub()
{
for (const auto& handle : Script::ScriptMainHandles)
for (const auto& handle : ScriptMainHandles)
{
const auto id = Game::Scr_ExecThread(handle, 0);
Logger::Print("Executing '{}::main'\n", handle.first.data());
const auto id = Game::Scr_ExecThread(handle.second, 0);
Game::Scr_FreeThread(static_cast<std::uint16_t>(id));
}
@ -224,9 +226,11 @@ namespace Components
void Script::Scr_StartupGameType_Stub()
{
for (const auto& handle : Script::ScriptInitHandles)
for (const auto& handle : ScriptInitHandles)
{
const auto id = Game::Scr_ExecThread(handle, 0);
Logger::Print("Executing '{}::init'\n", handle.first.data());
const auto id = Game::Scr_ExecThread(handle.second, 0);
Game::Scr_FreeThread(static_cast<std::uint16_t>(id));
}
@ -237,8 +241,8 @@ namespace Components
void Script::GScr_LoadGameTypeScript_Stub()
{
// Clear handles (from previous GSC loading session)
Script::ScriptMainHandles.clear();
Script::ScriptInitHandles.clear();
ScriptMainHandles.clear();
ScriptInitHandles.clear();
char path[MAX_PATH]{};
@ -262,18 +266,17 @@ namespace Components
}
Logger::Print("Script {}.gsc loaded successfully.\n", path);
Logger::Debug("Finding script handle main or init...");
const auto initHandle = Game::Scr_GetFunctionHandle(path, "init");
if (initHandle != 0)
{
Script::ScriptInitHandles.push_back(initHandle);
ScriptInitHandles.insert_or_assign(path, initHandle);
}
const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main");
if (mainHandle != 0)
{
Script::ScriptMainHandles.push_back(mainHandle);
ScriptMainHandles.insert_or_assign(path, mainHandle);
}
// Allow scripts with no handles
@ -285,7 +288,7 @@ namespace Components
void Script::AddFunction(const std::string& name, Game::BuiltinFunction func, bool type)
{
Script::ScriptFunction toAdd;
ScriptFunction toAdd;
toAdd.actionFunc = func;
toAdd.type = type;
@ -294,7 +297,7 @@ namespace Components
void Script::AddMethod(const std::string& name, Game::BuiltinMethod func, bool type)
{
Script::ScriptMethod toAdd;
ScriptMethod toAdd;
toAdd.actionFunc = func;
toAdd.type = type;
@ -306,7 +309,7 @@ namespace Components
if (pName != nullptr)
{
// If no function was found let's call game's function
if (const auto itr = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrFunctions.end())
if (const auto itr = CustomScrFunctions.find(Utils::String::ToLower(*pName)); itr != CustomScrFunctions.end())
{
*type = itr->second.type;
return itr->second.actionFunc;
@ -314,7 +317,7 @@ namespace Components
}
else
{
for (const auto& [name, builtin] : Script::CustomScrFunctions)
for (const auto& [name, builtin] : CustomScrFunctions)
{
Game::Scr_RegisterFunction(reinterpret_cast<int>(builtin.actionFunc), name.data());
}
@ -328,7 +331,7 @@ namespace Components
if (pName != nullptr)
{
// If no method was found let's call game's function
if (const auto itr = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); itr != Script::CustomScrMethods.end())
if (const auto itr = CustomScrMethods.find(Utils::String::ToLower(*pName)); itr != CustomScrMethods.end())
{
*type = itr->second.type;
return itr->second.actionFunc;
@ -336,7 +339,7 @@ namespace Components
}
else
{
for (const auto& [name, builtin] : Script::CustomScrMethods)
for (const auto& [name, builtin] : CustomScrMethods)
{
Game::Scr_RegisterFunction(reinterpret_cast<int>(builtin.actionFunc), name.data());
}
@ -347,7 +350,7 @@ namespace Components
void Script::StoreScriptBaseProgramNum()
{
Script::ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get<int>(0x1CFEEF8), Script::ScriptName);
ScriptBaseProgramNum.insert_or_assign(Utils::Hook::Get<int>(0x1CFEEF8), ScriptName);
}
void Script::Scr_PrintPrevCodePos(int scriptPos)
@ -355,7 +358,7 @@ namespace Components
auto bestCodePos = -1, nextCodePos = -1, offset = -1;
std::string file;
for (const auto& [key, value] : Script::ScriptBaseProgramNum)
for (const auto& [key, value] : ScriptBaseProgramNum)
{
const auto codePos = key;
@ -388,7 +391,7 @@ namespace Components
__asm
{
push esi
call Script::Scr_PrintPrevCodePos
call Scr_PrintPrevCodePos
add esp, 4h
pop esi
@ -402,7 +405,7 @@ namespace Components
{
// execute our hook
pushad
call Script::StoreScriptBaseProgramNum
call StoreScriptBaseProgramNum
popad
// execute overwritten code caused by the jump hook
@ -452,9 +455,9 @@ namespace Components
void Script::GetReplacedPos(const char* pos)
{
if (Script::ReplacedFunctions.contains(pos))
if (ReplacedFunctions.contains(pos))
{
Script::ReplacedPos = Script::ReplacedFunctions[pos];
ReplacedPos = ReplacedFunctions[pos];
}
}
@ -466,12 +469,12 @@ namespace Components
return;
}
if (Script::ReplacedFunctions.contains(what))
if (ReplacedFunctions.contains(what))
{
Logger::Warning(Game::CON_CHANNEL_SCRIPT, "ReplacedFunctions already contains codePosValue for a function\n");
}
Script::ReplacedFunctions[what] = with;
ReplacedFunctions[what] = with;
}
__declspec(naked) void Script::VMExecuteInternalStub()
@ -481,12 +484,12 @@ namespace Components
pushad
push edx
call Script::GetReplacedPos
call GetReplacedPos
pop edx
popad
cmp Script::ReplacedPos, 0
cmp ReplacedPos, 0
jne SetPos
movzx eax, byte ptr [edx]
@ -509,8 +512,8 @@ namespace Components
retn
SetPos:
mov edx, Script::ReplacedPos
mov Script::ReplacedPos, 0
mov edx, ReplacedPos
mov ReplacedPos, 0
movzx eax, byte ptr [edx]
inc edx
@ -521,7 +524,7 @@ namespace Components
Game::client_t* Script::GetClient(const Game::gentity_t* ent)
{
assert(ent != nullptr);
assert(ent);
if (ent->client == nullptr)
{
@ -529,7 +532,7 @@ namespace Components
return nullptr;
}
if (ent->s.number >= *Game::svs_clientCount)
if (static_cast<std::size_t>(ent->s.number) >= Game::MAX_CLIENTS)
{
Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number));
return nullptr;
@ -540,7 +543,7 @@ namespace Components
void Script::AddFunctions()
{
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
{
if (Game::Scr_GetNumParam() != 2)
{
@ -548,14 +551,14 @@ namespace Components
return;
}
const auto what = Script::GetCodePosForParam(0);
const auto with = Script::GetCodePosForParam(1);
const auto what = GetCodePosForParam(0);
const auto with = GetCodePosForParam(1);
Script::SetReplacedPos(what, with);
SetReplacedPos(what, with);
});
// System time
Script::AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds()
AddFunction("GetSystemMilliseconds", [] // gsc: GetSystemMilliseconds()
{
SYSTEMTIME time;
GetSystemTime(&time);
@ -564,7 +567,7 @@ namespace Components
});
// Executes command to the console
Script::AddFunction("Exec", [] // gsc: Exec(<string>)
AddFunction("Exec", [] // gsc: Exec(<string>)
{
const auto str = Game::Scr_GetString(0);
@ -578,7 +581,7 @@ namespace Components
});
// Allow printing to the console even when developer is 0
Script::AddFunction("PrintConsole", [] // gsc: PrintConsole(<string>)
AddFunction("PrintConsole", [] // gsc: PrintConsole(<string>)
{
for (std::size_t i = 0; i < Game::Scr_GetNumParam(); ++i)
{
@ -595,9 +598,9 @@ namespace Components
});
// PlayerCmd_AreControlsFrozen GSC function from Black Ops 2
Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* ent = Scr_GetPlayerEntity(entref);
Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0);
});
@ -605,34 +608,34 @@ namespace Components
Script::Script()
{
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, Script::RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, Script::StoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x612DB0, StoreFunctionNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427E71, RestoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x427DBC, StoreScriptNameStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x426C2D, StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x61E3AD, RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x621976, RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x62246E, RuntimeError, HOOK_CALL).install()->quick();
// Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError.
// On IW5 the function is entirely nullsubbed
Utils::Hook::Set<BYTE>(0x5F8DBF, 0xEB);
Utils::Hook::Set<std::uint8_t>(0x5F8DBF, 0xEB);
Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x434260, Script::CompileError, HOOK_JUMP).install()->quick();
Utils::Hook(0x612E8D, FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x434260, CompileError, HOOK_JUMP).install()->quick();
Utils::Hook(0x48EFFE, Script::Scr_LoadGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48F008, Script::Scr_StartupGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x45D44A, Script::GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48EFFE, Scr_LoadGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x48F008, Scr_StartupGameType_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x45D44A, GScr_LoadGameTypeScript_Stub, HOOK_CALL).install()->quick();
// Fetch custom functions
Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction
Utils::Hook(0x4EC8DD, Script::BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod
Utils::Hook(0x44E72E, BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction
Utils::Hook(0x4EC8DD, BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod
Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick();
Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick();
Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x61E92E, VMExecuteInternalStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x61E933, 1);
Scheduler::Loop([]
@ -642,9 +645,9 @@ namespace Components
const auto nowMs = Game::Sys_Milliseconds();
if (Script::LastFrameTime != -1)
if (LastFrameTime != -1)
{
const auto timeTaken = (nowMs - Script::LastFrameTime) * static_cast<int>((*Game::com_timescale)->current.value);
const auto timeTaken = (nowMs - LastFrameTime) * static_cast<int>((*Game::com_timescale)->current.value);
if (timeTaken >= 500)
{
@ -652,11 +655,11 @@ namespace Components
}
}
Script::LastFrameTime = nowMs;
LastFrameTime = nowMs;
}, Scheduler::Pipeline::SERVER);
#ifdef _DEBUG
Script::AddFunction("DebugBox", []
AddFunction("DebugBox", []
{
const auto* message = Game::Scr_GetString(0);
@ -666,14 +669,14 @@ namespace Components
}
MessageBoxA(nullptr, message, "DEBUG", MB_OK);
}, 1);
}, true);
#endif
Script::AddFunctions();
AddFunctions();
Events::OnVMShutdown([]
{
Script::ReplacedFunctions.clear();
ReplacedFunctions.clear();
});
}
}

View File

@ -16,6 +16,27 @@ namespace Components
static void ShowDeprecationWarning();
// Probably a macro 'originally' but this is fine
static Game::gentity_s* Scr_GetPlayerEntity(Game::scr_entref_t entref)
{
if (entref.classnum != 0)
{
Game::Scr_ObjectError("not an entity");
return nullptr;
}
assert(entref.entnum < Game::MAX_GENTITIES);
auto* ent = &Game::g_entities[entref.entnum];
if (ent->client == nullptr)
{
Game::Scr_ObjectError(Utils::String::VA("entity %i is not a player", entref.entnum));
return nullptr;
}
return ent;
}
private:
struct ScriptFunction
{
@ -38,8 +59,8 @@ namespace Components
static std::unordered_map<int, std::string> ScriptBaseProgramNum;
static int LastFrameTime;
static std::vector<int> ScriptMainHandles;
static std::vector<int> ScriptInitHandles;
static std::unordered_map<std::string, int> ScriptMainHandles;
static std::unordered_map<std::string, int> ScriptInitHandles;
static std::unordered_map<const char*, const char*> ReplacedFunctions;
static const char* ReplacedPos;

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{
@ -106,13 +107,13 @@ namespace Components
// Bounce
push 0x4B1B34
retn
ret
noBounce:
// Original game code
cmp dword ptr [esp + 0x24], 0
push 0x4B1B48
retn
ret
}
}
@ -186,6 +187,18 @@ namespace Components
}
}
void PlayerMovement::GScr_IsSprinting(const Game::scr_entref_t entref)
{
const auto* client = Game::GetEntity(entref)->client;
if (!client)
{
Game::Scr_Error("IsSprinting can only be called on a player");
return;
}
Game::Scr_AddBool(Game::PM_IsSprinting(&client->ps));
}
const Game::dvar_t* PlayerMovement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value,
float min, float max, unsigned __int16 /*flags*/, const char* description)
{
@ -233,6 +246,9 @@ namespace Components
PlayerMovement::PlayerMovement()
{
AssertOffset(Game::playerState_s, eFlags, 0xB0);
AssertOffset(Game::playerState_s, pm_flags, 0xC);
Scheduler::Once([]
{
static const char* bg_bouncesValues[] =
@ -273,6 +289,8 @@ namespace Components
Utils::Hook(0x45A5BF, CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity
Utils::Hook(0x5A0CAD, CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity
Script::AddMethod("IsSprinting", GScr_IsSprinting);
RegisterMovementDvars();
}
}

View File

@ -41,6 +41,8 @@ namespace Components
static int StuckInClient_Hk(Game::gentity_s* self);
static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles);
static void GScr_IsSprinting(Game::scr_entref_t entref);
static const Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description);
static void RegisterMovementDvars();

View File

@ -233,6 +233,7 @@ namespace Game
PM_Trace_t PM_Trace = PM_Trace_t(0x441F60);
PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540);
PM_UpdateLean_t PM_UpdateLean = PM_UpdateLean_t(0x43DED0);
PM_IsSprinting_t PM_IsSprinting = PM_IsSprinting_t(0x4B3830);
IN_RecenterMouse_t IN_RecenterMouse = IN_RecenterMouse_t(0x463D80);

View File

@ -524,6 +524,9 @@ namespace Game
typedef void(*PM_UpdateLean_t)(playerState_s* ps, float msec, usercmd_s* cmd, void(*capsuleTrace)(trace_t*, const float*, const float*, const Bounds*, int, int));
extern PM_UpdateLean_t PM_UpdateLean;
typedef bool(*PM_IsSprinting_t)(const playerState_s* ps);
extern PM_IsSprinting_t PM_IsSprinting;
typedef void(*IN_RecenterMouse_t)();
extern IN_RecenterMouse_t IN_RecenterMouse;

View File

@ -1679,23 +1679,23 @@ namespace Game
enum usercmdButtonBits
{
CMD_BUTTON_ATTACK = 0x1,
CMD_BUTTON_SPRINT = 0x2,
CMD_BUTTON_MELEE = 0x4,
CMD_BUTTON_ACTIVATE = 0x8,
CMD_BUTTON_RELOAD = 0x10,
CMD_BUTTON_USE_RELOAD = 0x20,
CMD_BUTTON_LEAN_LEFT = 0x40,
CMD_BUTTON_LEAN_RIGHT = 0x80,
CMD_BUTTON_PRONE = 0x100,
CMD_BUTTON_CROUCH = 0x200,
CMD_BUTTON_UP = 0x400,
CMD_BUTTON_ADS = 0x800,
CMD_BUTTON_DOWN = 0x1000,
CMD_BUTTON_BREATH = 0x2000,
CMD_BUTTON_FRAG = 0x4000,
CMD_BUTTON_OFFHAND_SECONDARY = 0x8000,
CMD_BUTTON_THROW = 0x80000
CMD_BUTTON_ATTACK = 1 << 0,
CMD_BUTTON_SPRINT = 1 << 1,
CMD_BUTTON_MELEE = 1 << 2,
CMD_BUTTON_ACTIVATE = 1 << 3,
CMD_BUTTON_RELOAD = 1 << 4,
CMD_BUTTON_USE_RELOAD = 1 << 5,
CMD_BUTTON_LEAN_LEFT = 1 << 6,
CMD_BUTTON_LEAN_RIGHT = 1 << 7,
CMD_BUTTON_PRONE = 1 << 8,
CMD_BUTTON_CROUCH = 1 << 9,
CMD_BUTTON_UP = 1 << 10,
CMD_BUTTON_ADS = 1 << 11,
CMD_BUTTON_DOWN = 1 << 12,
CMD_BUTTON_BREATH = 1 << 13,
CMD_BUTTON_FRAG = 1 << 14,
CMD_BUTTON_OFFHAND_SECONDARY = 1 << 15,
CMD_BUTTON_THROW = 1 << 19,
};
#pragma pack(push, 4)