Merge pull request #46 from ineedbots/bot_scr

Bot GSC functions for bots pressing buttons and moving + fix for node system related crash
This commit is contained in:
Dss0 2020-12-05 02:41:08 +01:00 committed by GitHub
commit 8d1b06203e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 587 additions and 22 deletions

View File

@ -1,12 +1,94 @@
#include "STDInclude.hpp"
#define KEY_MASK_FIRE 1
#define KEY_MASK_SPRINT 2
#define KEY_MASK_MELEE 4
#define KEY_MASK_RELOAD 16
#define KEY_MASK_LEANLEFT 64
#define KEY_MASK_LEANRIGHT 128
#define KEY_MASK_PRONE 256
#define KEY_MASK_CROUCH 512
#define KEY_MASK_JUMP 1024
#define KEY_MASK_ADS_MODE 2048
#define KEY_MASK_TEMP_ACTION 4096
#define KEY_MASK_HOLDBREATH 8192
#define KEY_MASK_FRAG 16384
#define KEY_MASK_SMOKE 32768
#define KEY_MASK_NIGHTVISION 262144
#define KEY_MASK_ADS 524288
#define KEY_MASK_USE 0x28
#define MAX_G_BOTAI_ENTRIES 18
namespace Components
{
std::vector<std::string> Bots::BotNames;
typedef struct BotMovementInfo_t
{
/* Actions */
int buttons;
/* Movement */
int8 forward;
int8 right;
/* Weapon */
unsigned short weapon;
} BotMovementInfo_t;
static BotMovementInfo_t g_botai[MAX_G_BOTAI_ENTRIES];
struct BotAction_t
{
const char* action;
int key;
};
static const BotAction_t BotActions[] =
{
{ "gostand", KEY_MASK_JUMP },
{ "gocrouch", KEY_MASK_CROUCH },
{ "goprone", KEY_MASK_PRONE },
{ "fire", KEY_MASK_FIRE },
{ "melee", KEY_MASK_MELEE },
{ "frag", KEY_MASK_FRAG },
{ "smoke", KEY_MASK_SMOKE },
{ "reload", KEY_MASK_RELOAD },
{ "sprint", KEY_MASK_SPRINT },
{ "leanleft", KEY_MASK_LEANLEFT },
{ "leanright", KEY_MASK_LEANRIGHT },
{ "ads", KEY_MASK_ADS_MODE },
{ "holdbreath", KEY_MASK_HOLDBREATH },
{ "use", KEY_MASK_USE },
{ "0", 8 },
{ "1", 32 },
{ "2", 65536 },
{ "3", 131072 },
{ "4", 1048576 },
{ "5", 2097152 },
{ "6", 4194304 },
{ "7", 8388608 },
{ "8", 16777216 },
{ "9", 33554432 },
};
unsigned int Bots::GetClientNum(Game::client_s* cl)
{
unsigned int num;
num = ((byte*)cl - (byte*)Game::svs_clients) / sizeof(Game::client_s);
return num;
}
bool Bots::IsValidClientNum(unsigned int cNum)
{
return (cNum >= 0) && (cNum < (unsigned int)*Game::svs_numclients);
}
void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port)
{
static int botId = 0;
const char* botName;
if (Bots::BotNames.empty())
{
@ -27,15 +109,19 @@ namespace Components
}
}
}
}
if (Bots::BotNames.empty())
if (!Bots::BotNames.empty())
{
Bots::BotNames.push_back("bot");
botId %= Bots::BotNames.size();
botName = Bots::BotNames[botId++].data();
}
else
{
botName = Utils::String::VA("bot%d", ++botId);
}
botId %= Bots::BotNames.size();
strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, Bots::BotNames[botId++].data(), protocol, checksum, statVer, statStuff, port), 0x400);
strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, botName, protocol, checksum, statVer, statStuff, port), 0x400);
}
void Bots::Spawn(unsigned int count)
@ -70,6 +156,241 @@ namespace Components
}
}
void Bots::AddMethods()
{
Script::AddFunction("SetPing", [](Game::scr_entref_t id) // gsc: self SetPing(<int>)
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_INTEGER)
{
Game::Scr_Error("^1SetPing: Needs one integer parameter!\n");
return;
}
auto ping = Game::Scr_GetInt(0);
if (ping < 0 || ping > 999)
{
Game::Scr_Error("^1SetPing: Ping needs to between 0 and 999!\n");
return;
}
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1SetPing: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1SetPing: Need to call on a connected player!\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1SetPing: Can only call on a bot!\n");
return;
}
client->ping = (short)ping;
});
Script::AddFunction("isBot", [](Game::scr_entref_t id) // Usage: <bot> isBot();
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1isBot: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1isBot: Needs to be connected.\n");
return;
}
Game::Scr_AddInt(client->isBot);
});
Script::AddFunction("botStop", [](Game::scr_entref_t id) // Usage: <bot> botStop();
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botStop: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botStop: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botStop: Can only call on a bot!\n");
return;
}
g_botai[clientNum] = { 0 };
g_botai[clientNum].weapon = 1;
});
Script::AddFunction("botWeapon", [](Game::scr_entref_t id) // Usage: <bot> botWeapon(<str>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1botWeapon: Needs one string parameter!\n");
return;
}
auto weapon = Game::Scr_GetString(0);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botWeapon: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botWeapon: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botWeapon: Can only call on a bot!\n");
return;
}
if (weapon == ""s)
{
g_botai[clientNum].weapon = 1;
return;
}
int weapId = Game::G_GetWeaponIndexForName(weapon);
g_botai[clientNum].weapon = (unsigned short)weapId;
});
Script::AddFunction("botAction", [](Game::scr_entref_t id) // Usage: <bot> botAction(<str action>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1botAction: Needs one string parameter!\n");
return;
}
auto action = Game::Scr_GetString(0);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botAction: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botAction: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botAction: Can only call on a bot!\n");
return;
}
if (action[0] != '+' && action[0] != '-')
{
Game::Scr_Error("^1botAction: Sign for action must be '+' or '-'.\n");
return;
}
for (size_t i = 0; i < sizeof(BotActions) / sizeof(BotAction_t); ++i)
{
if (strcmp(&action[1], BotActions[i].action))
continue;
if (action[0] == '+')
g_botai[clientNum].buttons |= BotActions[i].key;
else
g_botai[clientNum].buttons &= ~(BotActions[i].key);
return;
}
Game::Scr_Error("^1botAction: Unknown action.\n");
});
Script::AddFunction("botMovement", [](Game::scr_entref_t id) // Usage: <bot> botMovement(<int>, <int>);
{
if (Game::Scr_GetNumParam() != 2 || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER)
{
Game::Scr_Error("^1botMovement: Needs two integer parameters!\n");
return;
}
auto forwardInt = Game::Scr_GetInt(0);
auto rightInt = Game::Scr_GetInt(1);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1botMovement: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botMovement: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botMovement: Can only call on a bot!\n");
return;
}
if (forwardInt > 127)
forwardInt = 127;
if (forwardInt < -127)
forwardInt = -127;
if (rightInt > 127)
rightInt = 127;
if (rightInt < -127)
rightInt = -127;
g_botai[clientNum].forward = (int8)forwardInt;
g_botai[clientNum].right = (int8)rightInt;
});
}
Bots::Bots()
{
// Replace connect string
@ -78,6 +399,50 @@ namespace Components
// Intercept sprintf for the connect string
Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick();
// Stop default behavour of bots spinning and shooting
Utils::Hook(0x627021, 0x4BB9B0, HOOK_CALL).install()->quick();
Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick();
// zero the bot command array
for (int i = 0; i < MAX_G_BOTAI_ENTRIES; i++)
{
g_botai[i] = { 0 };
g_botai[i].weapon = 1; // prevent the bots from defaulting to the 'none' weapon
}
// have the bots perform the command every server frame
Scheduler::OnFrame([]()
{
if (!Game::SV_Loaded())
return;
int time = *Game::svs_time;
int numClients = *Game::svs_numclients;
for (int i = 0; i < numClients; ++i)
{
Game::client_t* client = &Game::svs_clients[i];
if (client->state < 3)
continue;
if (!client->isBot)
continue;
Game::usercmd_s ucmd = { 0 };
ucmd.serverTime = time;
ucmd.buttons = g_botai[i].buttons;
ucmd.forwardmove = g_botai[i].forward;
ucmd.rightmove = g_botai[i].right;
ucmd.weapon = g_botai[i].weapon;
client->deltaMessage = client->outgoingSequence - 1;
Game::SV_ClientThink(client, &ucmd);
}
});
Command::Add("spawnBot", [](Command::Params* params)
{
unsigned int count = 1;
@ -103,6 +468,8 @@ namespace Components
Bots::Spawn(count);
});
Bots::AddMethods();
}
Bots::~Bots()

View File

@ -7,6 +7,8 @@ namespace Components
public:
Bots();
~Bots();
static unsigned int GetClientNum(Game::client_s*);
static bool IsValidClientNum(unsigned int);
private:
static std::vector<std::string> BotNames;
@ -14,5 +16,7 @@ namespace Components
static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port);
static void Spawn(unsigned int count);
static void AddMethods();
};
}

View File

@ -336,6 +336,12 @@ namespace Components
}
}
void Network::NET_DeferPacketToClientStub(Game::netadr_t* from, Game::msg_t* msg)
{
if (msg->cursize > 0 && msg->cursize <= 1404)
Game::NET_DeferPacketToClient(from, msg);
}
Network::Network()
{
AssertSize(Game::netadr_t, 20);
@ -372,6 +378,9 @@ namespace Components
// Install packet deploy hook
Utils::Hook::RedirectJump(0x5AA713, Network::DeployPacketStub);
// Fix packets causing buffer overflow
Utils::Hook(0x6267E3, Network::NET_DeferPacketToClientStub, HOOK_CALL).install()->quick();
Network::Handle("resolveAddress", [](Address address, const std::string& /*data*/)
{
Network::SendRaw(address, address.getString());

View File

@ -88,6 +88,7 @@ namespace Components
static void NetworkStartStub();
static void PacketErrorCheck();
static void NET_DeferPacketToClientStub(Game::netadr_t* from, Game::msg_t* msg);
};
}

View File

@ -278,26 +278,49 @@ namespace Components
}
void Node::SendList(Network::Address address)
{
std::lock_guard<std::recursive_mutex> _(Node::Mutex);
// need to keep the message size below 1404 bytes else recipient will just drop it
std::vector<std::string> nodeListReponseMessages;
for (size_t curNode = 0; curNode < Node::Nodes.size();)
{
Proto::Node::List list;
list.set_isnode(Dedicated::IsEnabled());
list.set_protocol(PROTOCOL);
list.set_port(Node::GetPort());
std::lock_guard<std::recursive_mutex> _(Node::Mutex);
for (auto& node : Node::Nodes)
for (size_t i = 0; i < NODE_MAX_NODES_TO_SEND;)
{
if (curNode >= Node::Nodes.size())
break;
auto node = Node::Nodes.at(curNode++);
if (node.isValid())
{
std::string* str = list.add_nodes();
sockaddr addr = node.address.getSockAddr();
str->append(reinterpret_cast<char*>(&addr), sizeof(addr));
i++;
}
}
Session::Send(address, "nodeListResponse", list.SerializeAsString());
nodeListReponseMessages.push_back(list.SerializeAsString());
}
size_t i = 0;
for (auto& nodeListData : nodeListReponseMessages)
{
Scheduler::OnDelay([nodeListData, i, address]()
{
NODE_LOG("Sending %d nodeListResponse length to %s\n", nodeListData.length(), address.getCString());
Session::Send(address, "nodeListResponse", nodeListData);
}, NODE_SEND_RATE * i++);
}
}
unsigned short Node::GetPort()

View File

@ -1,6 +1,8 @@
#pragma once
#define NODE_HALFLIFE (3 * 60 * 1000) //3min
#define NODE_MAX_NODES_TO_SEND 64
#define NODE_SEND_RATE 500ms
#ifdef NODE_LOG_MESSAGES
#define NODE_LOG(x, ...) Logger::Print(x, __VA_ARGS__)

View File

@ -7,6 +7,7 @@ namespace Components
std::vector<Script::Function> Script::ScriptFunctions;
std::vector<std::string> Script::ScriptNameStack;
unsigned short Script::FunctionName;
std::unordered_map<std::string, std::string> Script::ScriptStorage;
Utils::Signal<Scheduler::Callback> Script::VMShutdownSignal;
@ -331,6 +332,127 @@ namespace Components
return &Game::svs_clients[gentity->number];
}
void Script::AddFunctions()
{
// System time
Script::AddFunction("GetSystemTime", [](Game::scr_entref_t) // gsc: GetSystemTime()
{
SYSTEMTIME time;
GetSystemTime(&time);
Game::Scr_AddInt(time.wSecond);
});
Script::AddFunction("GetSystemMilliseconds", [](Game::scr_entref_t) // gsc: GetSystemMilliseconds()
{
SYSTEMTIME time;
GetSystemTime(&time);
Game::Scr_AddInt(time.wMilliseconds);
});
// Print to console, even without being in 'developer 1'.
Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole(<string>)
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1PrintConsole: Needs one string parameter!\n");
return;
}
auto str = Game::Scr_GetString(0);
Game::Com_Printf(0, str);
});
// Executes command to the console
Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec(<string>)
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1Exec: Needs one string parameter!\n");
return;
}
auto str = Game::Scr_GetString(0);
Command::Execute(str, false);
});
// Script Storage Funcs
Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(<str key>, <str data>);
{
if (Game::Scr_GetNumParam() != 2 || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageSet: Needs two string parameters!\n");
return;
}
std::string key = Game::Scr_GetString(0);
std::string data = Game::Scr_GetString(1);
Script::ScriptStorage.insert_or_assign(key, data);
});
Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(<str key>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageRemove: Needs one string parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
if (!Script::ScriptStorage.contains(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.c_str()));
return;
}
Script::ScriptStorage.erase(key);
});
Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(<str key>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageGet: Needs one string parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
if (!Script::ScriptStorage.contains(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.c_str()));
return;
}
auto data = Script::ScriptStorage.at(key);
Game::Scr_AddString(data.c_str());
});
Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(<str key>);
{
if (Game::Scr_GetNumParam() != 1 || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1StorageHas: Needs one string parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
Game::Scr_AddInt(Script::ScriptStorage.contains(key));
});
Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear();
{
Script::ScriptStorage.clear();
});
}
Script::Script()
{
Utils::Hook(0x612DB0, Script::StoreFunctionNameStub, HOOK_JUMP).install()->quick();
@ -357,6 +479,8 @@ namespace Components
MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0);
}, true);
Script::AddFunctions();
// Script::AddFunction("playviewmodelfx", [](Game::scr_entref_t /*index*/)
// {
// /*auto Scr_Error = Utils::Hook::Call<void(const char*)>(0x42EF40);
@ -391,5 +515,7 @@ namespace Components
Script::ScriptNameStack.clear();
Script::ScriptFunctions.clear();
Script::VMShutdownSignal.clear();
Script::ScriptStorage.clear();
}
}

View File

@ -38,6 +38,7 @@ namespace Components
static std::vector<Function> ScriptFunctions;
static std::vector<std::string> ScriptNameStack;
static unsigned short FunctionName;
static std::unordered_map<std::string, std::string> ScriptStorage;
static Utils::Signal<Scheduler::Callback> VMShutdownSignal;
@ -62,5 +63,7 @@ namespace Components
static void ScrShutdownSystemStub(int);
static int SetExpFogStub();
static void AddFunctions();
};
}

View File

@ -136,6 +136,7 @@ namespace Game
FS_BuildPathToFile_t FS_BuildPathToFile = FS_BuildPathToFile_t(0x4702C0);
FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440);
G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540);
G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840);
GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0);
@ -194,6 +195,7 @@ namespace Game
NET_AdrToString_t NET_AdrToString = NET_AdrToString_t(0x469880);
NET_CompareAdr_t NET_CompareAdr = NET_CompareAdr_t(0x4D0AA0);
NET_DeferPacketToClient_t NET_DeferPacketToClient = NET_DeferPacketToClient_t(0x4C8AA0);
NET_ErrorString_t NET_ErrorString = NET_ErrorString_t(0x4E7720);
NET_Init_t NET_Init = NET_Init_t(0x491860);
NET_IsLocalAddress_t NET_IsLocalAddress = NET_IsLocalAddress_t(0x402BD0);
@ -253,6 +255,8 @@ namespace Game
Scr_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40);
Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750);
Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30);
Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0);
Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900);
Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0);
@ -293,6 +297,7 @@ namespace Game
SV_DirectConnect_t SV_DirectConnect = SV_DirectConnect_t(0x460480);
SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0);
SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0);
SV_ClientThink_t SV_ClientThink = SV_ClientThink_t(0x44ADD0);
Sys_Error_t Sys_Error = Sys_Error_t(0x4E0200);
Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580);
@ -342,6 +347,7 @@ namespace Game
source_t **sourceFiles = reinterpret_cast<source_t **>(0x7C4A98);
keywordHash_t **menuParseKeywordHash = reinterpret_cast<keywordHash_t **>(0x63AE928);
int* svs_time = reinterpret_cast<int*>(0x31D9384);
int* svs_numclients = reinterpret_cast<int*>(0x31D938C);
client_t* svs_clients = reinterpret_cast<client_t*>(0x31D9390);

View File

@ -317,6 +317,9 @@ namespace Game
typedef iwd_t*(__cdecl * FS_IsShippedIWD_t)(const char* fullpath, const char* iwd);
extern FS_IsShippedIWD_t FS_IsShippedIWD;
typedef int(__cdecl* G_GetWeaponIndexForName_t)(char*);
extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName;
typedef void(__cdecl* G_SpawnEntitiesFromString_t)();
extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString;
@ -462,6 +465,9 @@ namespace Game
typedef bool(__cdecl * NET_CompareAdr_t)(netadr_t a, netadr_t b);
extern NET_CompareAdr_t NET_CompareAdr;
typedef void(__cdecl * NET_DeferPacketToClient_t)(netadr_t *, msg_t *);
extern NET_DeferPacketToClient_t NET_DeferPacketToClient;
typedef const char* (__cdecl * NET_ErrorString_t)();
extern NET_ErrorString_t NET_ErrorString;
@ -615,6 +621,12 @@ namespace Game
typedef bool(__cdecl * Scr_IsSystemActive_t)();
extern Scr_IsSystemActive_t Scr_IsSystemActive;
typedef int(__cdecl* Scr_GetType_t)(int);
extern Scr_GetType_t Scr_GetType;
typedef void(__cdecl* Scr_Error_t)(const char*);
extern Scr_Error_t Scr_Error;
typedef script_t* (__cdecl * Script_Alloc_t)(int length);
extern Script_Alloc_t Script_Alloc;
@ -678,6 +690,9 @@ namespace Game
typedef bool(__cdecl * SV_Loaded_t)();
extern SV_Loaded_t SV_Loaded;
typedef void(__cdecl* SV_ClientThink_t)(client_s*, usercmd_s*);
extern SV_ClientThink_t SV_ClientThink;
typedef int(__cdecl * Sys_Error_t)(int, char *, ...);
extern Sys_Error_t Sys_Error;
@ -769,6 +784,7 @@ namespace Game
extern cmd_function_t** cmd_functions;
extern int* svs_time;
extern int* svs_numclients;
extern client_t* svs_clients;

View File

@ -4481,7 +4481,15 @@ namespace Game
// 0
clientstate_t state;
// 4
char pad[36];
char _pad[4];
// 8
int deltaMessage;
// 12
char __pad[12];
// 24
int outgoingSequence;
// 28
char pad[12];
// 40
netadr_t addr;
// 60