diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp new file mode 100644 index 00000000..99988189 --- /dev/null +++ b/src/client/component/scripting.cpp @@ -0,0 +1,119 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include + +#include "game/scripting/entity.hpp" +#include "game/scripting/functions.hpp" +#include "game/scripting/event.hpp" +#include "game/scripting/lua/engine.hpp" +#include "game/scripting/execution.hpp" + +#include "scheduler.hpp" +#include "scripting.hpp" + +namespace scripting +{ + std::unordered_map> script_function_table; + + namespace + { + utils::hook::detour vm_notify_hook; + utils::hook::detour scr_load_level_hook; + utils::hook::detour g_shutdown_game_hook; + + utils::hook::detour scr_set_thread_position_hook; + utils::hook::detour process_script_hook; + + std::string current_file; + + void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value, + game::VariableValue* top) + { + if (!game::VirtualLobby_Loaded()) + { + const auto* string = game::SL_ConvertToString(string_value); + if (string) + { + event e; + e.name = string; + e.entity = notify_list_owner_id; + + for (auto* value = top; value->type != game::SCRIPT_END; --value) + { + e.arguments.emplace_back(*value); + } + + if (e.name == "connected") + { + scripting::clear_entity_fields(e.entity); + } + + lua::engine::notify(e); + } + } + + vm_notify_hook.invoke(notify_list_owner_id, string_value, top); + } + + void scr_load_level_stub() + { + scr_load_level_hook.invoke(); + if (!game::VirtualLobby_Loaded()) + { + lua::engine::start(); + } + } + + void g_shutdown_game_stub(const int free_scripts) + { + lua::engine::stop(); + return g_shutdown_game_hook.invoke(free_scripts); + } + + void process_script_stub(const char* filename) + { + const auto file_id = atoi(filename); + if (file_id) + { + current_file = scripting::find_token(file_id); + } + else + { + current_file = filename; + } + + process_script_hook.invoke(filename); + } + + void scr_set_thread_position_stub(unsigned int threadName, const char* codePos) + { + const auto function_name = scripting::find_token(threadName); + script_function_table[current_file][function_name] = codePos; + scr_set_thread_position_hook.invoke(threadName, codePos); + } + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + vm_notify_hook.create(SELECT_VALUE(0x140320E50, 0x1404479F0 ), vm_notify_stub); // H1MP + // SP address is wrong, but should be ok + scr_load_level_hook.create(SELECT_VALUE(0x140005260, 0x1403727C0), scr_load_level_stub); // H1MP + g_shutdown_game_hook.create(SELECT_VALUE(0x140228BA0, 0x140345A60), g_shutdown_game_stub); // H1MP + + scr_set_thread_position_hook.create(SELECT_VALUE(0x1403115E0, 0x140437D10), scr_set_thread_position_stub); // H1MP + process_script_hook.create(SELECT_VALUE(0x14031AB30, 0x1404417E0), process_script_stub); // H1MP + + scheduler::loop([]() + { + lua::engine::run_frame(); + }, scheduler::pipeline::server); + } + }; +} + +REGISTER_COMPONENT(scripting::component) \ No newline at end of file diff --git a/src/client/component/scripting.hpp b/src/client/component/scripting.hpp new file mode 100644 index 00000000..5794bff2 --- /dev/null +++ b/src/client/component/scripting.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace scripting +{ + using shared_table_t = std::unordered_map; + + extern std::unordered_map> fields_table; + extern std::unordered_map> script_function_table; + extern utils::concurrency::container shared_table; +} \ No newline at end of file diff --git a/src/client/game/scripting/entity.cpp b/src/client/game/scripting/entity.cpp new file mode 100644 index 00000000..b81dea1b --- /dev/null +++ b/src/client/game/scripting/entity.cpp @@ -0,0 +1,120 @@ +#include +#include "entity.hpp" +#include "script_value.hpp" +#include "execution.hpp" + +namespace scripting +{ + entity::entity() + : entity(0) + { + } + + entity::entity(const entity& other) : entity(other.entity_id_) + { + } + + entity::entity(entity&& other) noexcept + { + this->entity_id_ = other.entity_id_; + other.entity_id_ = 0; + } + + entity::entity(const unsigned int entity_id) + : entity_id_(entity_id) + { + this->add(); + } + + entity::entity(game::scr_entref_t entref) + : entity(game::FindEntityId(entref.entnum, entref.classnum)) + { + } + + entity::~entity() + { + this->release(); + } + + entity& entity::operator=(const entity& other) + { + if (&other != this) + { + this->release(); + this->entity_id_ = other.entity_id_; + this->add(); + } + + return *this; + } + + entity& entity::operator=(entity&& other) noexcept + { + if (&other != this) + { + this->release(); + this->entity_id_ = other.entity_id_; + other.entity_id_ = 0; + } + + return *this; + } + + unsigned int entity::get_entity_id() const + { + return this->entity_id_; + } + + game::scr_entref_t entity::get_entity_reference() const + { + if (!this->entity_id_) + { + const auto not_null = static_cast(~0ui16); + return game::scr_entref_t{not_null, not_null}; + } + + return game::Scr_GetEntityIdRef(this->get_entity_id()); + } + + bool entity::operator==(const entity& other) const noexcept + { + return this->get_entity_id() == other.get_entity_id(); + } + + bool entity::operator!=(const entity& other) const noexcept + { + return !this->operator==(other); + } + + void entity::add() const + { + if (this->entity_id_) + { + game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast(this->entity_id_)}); + } + } + + void entity::release() const + { + if (this->entity_id_) + { + game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast(this->entity_id_)}); + } + } + + void entity::set(const std::string& field, const script_value& value) const + { + set_entity_field(*this, field, value); + } + + template <> + script_value entity::get(const std::string& field) const + { + return get_entity_field(*this, field); + } + + script_value entity::call(const std::string& name, const std::vector& arguments) const + { + return call_function(name, *this, arguments); + } +} diff --git a/src/client/game/scripting/entity.hpp b/src/client/game/scripting/entity.hpp new file mode 100644 index 00000000..b1702379 --- /dev/null +++ b/src/client/game/scripting/entity.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "game/game.hpp" +#include "script_value.hpp" + +namespace scripting +{ + class entity final + { + public: + entity(); + entity(unsigned int entity_id); + entity(game::scr_entref_t entref); + + entity(const entity& other); + entity(entity&& other) noexcept; + + ~entity(); + + entity& operator=(const entity& other); + entity& operator=(entity&& other) noexcept; + + void set(const std::string& field, const script_value& value) const; + + template + T get(const std::string& field) const; + + script_value call(const std::string& name, const std::vector& arguments = {}) const; + + unsigned int get_entity_id() const; + game::scr_entref_t get_entity_reference() const; + + bool operator ==(const entity& other) const noexcept; + bool operator !=(const entity& other) const noexcept; + + private: + unsigned int entity_id_; + + void add() const; + void release() const; + }; + + template <> + script_value entity::get(const std::string& field) const; + + template + T entity::get(const std::string& field) const + { + return this->get(field).as(); + } +} diff --git a/src/client/game/scripting/event.hpp b/src/client/game/scripting/event.hpp new file mode 100644 index 00000000..bc1d53e0 --- /dev/null +++ b/src/client/game/scripting/event.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "script_value.hpp" +#include "entity.hpp" + +namespace scripting +{ + struct event + { + std::string name; + entity entity{}; + std::vector arguments; + }; +} diff --git a/src/client/game/scripting/execution.cpp b/src/client/game/scripting/execution.cpp new file mode 100644 index 00000000..ba4e4abd --- /dev/null +++ b/src/client/game/scripting/execution.cpp @@ -0,0 +1,250 @@ +#include +#include "execution.hpp" +#include "safe_execution.hpp" +#include "stack_isolation.hpp" + +#include "component/scripting.hpp" + +namespace scripting +{ + namespace + { + game::VariableValue* allocate_argument() + { + game::VariableValue* value_ptr = ++game::scr_VmPub->top; + ++game::scr_VmPub->inparamcount; + return value_ptr; + } + + void push_value(const script_value& value) + { + auto* value_ptr = allocate_argument(); + *value_ptr = value.get_raw(); + + game::AddRefToValue(value_ptr->type, value_ptr->u); + } + + int get_field_id(const int classnum, const std::string& field) + { + const auto class_id = game::g_classMap[classnum].id; + const auto field_str = game::SL_GetString(field.data(), 0); + const auto _ = gsl::finally([field_str]() + { + game::RemoveRefToValue(game::SCRIPT_STRING, {static_cast(field_str)}); + }); + + const auto offset = game::FindVariable(class_id, field_str); + if (offset) + { + const auto index = 3 * (offset + 0xFA00 * (class_id & 3)); + const auto id = reinterpret_cast(SELECT_VALUE(0x149BB5680, 0x14821DF80))[index]; + return static_cast(id); + } + + return -1; + } + + script_value get_return_value() + { + if (game::scr_VmPub->inparamcount == 0) + { + return {}; + } + + game::Scr_ClearOutParams(); + game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount; + game::scr_VmPub->inparamcount = 0; + + return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]); + } + } + + void notify(const entity& entity, const std::string& event, const std::vector& arguments) + { + stack_isolation _; + for (auto i = arguments.rbegin(); i != arguments.rend(); ++i) + { + push_value(*i); + } + + const auto event_id = game::SL_GetString(event.data(), 0); + game::Scr_NotifyId(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount); + } + + script_value call_function(const std::string& name, const entity& entity, + const std::vector& arguments) + { + const auto entref = entity.get_entity_reference(); + + const auto is_method_call = *reinterpret_cast(&entref) != -1; + const auto function = find_function(name, !is_method_call); + if (function == nullptr) + { + throw std::runtime_error("Unknown "s + (is_method_call ? "method" : "function") + " '" + name + "'"); + } + + stack_isolation _; + + for (auto i = arguments.rbegin(); i != arguments.rend(); ++i) + { + push_value(*i); + } + + game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount; + game::scr_VmPub->inparamcount = 0; + + if (!safe_execution::call(function, entref)) + { + throw std::runtime_error( + "Error executing "s + (is_method_call ? "method" : "function") + " '" + name + "'"); + } + + return get_return_value(); + } + + script_value call_function(const std::string& name, const std::vector& arguments) + { + return call_function(name, entity(), arguments); + } + + template <> + script_value call(const std::string& name, const std::vector& arguments) + { + return call_function(name, arguments); + } + + script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector& arguments) + { + const auto id = entity.get_entity_id(); + + stack_isolation _; + for (auto i = arguments.rbegin(); i != arguments.rend(); ++i) + { + scripting::push_value(*i); + } + + game::AddRefToObject(id); + + const auto local_id = game::AllocThread(id); + const auto result = game::VM_Execute(local_id, pos, (unsigned int)arguments.size()); + game::RemoveRefToObject(result); + + return get_return_value(); + } + + const char* get_function_pos(const std::string& filename, const std::string& function) + { + if (scripting::script_function_table.find(filename) == scripting::script_function_table.end()) + { + throw std::runtime_error("File '" + filename + "' not found"); + }; + + const auto functions = scripting::script_function_table[filename]; + if (functions.find(function) == functions.end()) + { + throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found"); + } + + return functions.at(function); + } + + script_value call_script_function(const entity& entity, const std::string& filename, + const std::string& function, const std::vector& arguments) + { + const auto pos = get_function_pos(filename, function); + return exec_ent_thread(entity, pos, arguments); + } + + static std::unordered_map> custom_fields; + + script_value get_custom_field(const entity& entity, const std::string& field) + { + auto& fields = custom_fields[entity.get_entity_id()]; + const auto _field = fields.find(field); + if (_field != fields.end()) + { + return _field->second; + } + return {}; + } + + void set_custom_field(const entity& entity, const std::string& field, const script_value& value) + { + const auto id = entity.get_entity_id(); + + if (custom_fields[id].find(field) != custom_fields[id].end()) + { + custom_fields[id][field] = value; + return; + } + + custom_fields[id].insert(std::make_pair(field, value)); + } + + void clear_entity_fields(const entity& entity) + { + const auto id = entity.get_entity_id(); + + if (custom_fields.find(id) != custom_fields.end()) + { + custom_fields[id].clear(); + } + } + + void clear_custom_fields() + { + custom_fields.clear(); + } + + void set_entity_field(const entity& entity, const std::string& field, const script_value& value) + { + const auto entref = entity.get_entity_reference(); + const int id = get_field_id(entref.classnum, field); + + if (id != -1) + { + stack_isolation _; + push_value(value); + + game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount; + game::scr_VmPub->inparamcount = 0; + + if (!safe_execution::set_entity_field(entref, id)) + { + throw std::runtime_error("Failed to set value for field '" + field + "'"); + } + } + else + { + // Read custom fields + set_custom_field(entity, field, value); + } + } + + script_value get_entity_field(const entity& entity, const std::string& field) + { + const auto entref = entity.get_entity_reference(); + const auto id = get_field_id(entref.classnum, field); + + if (id != -1) + { + stack_isolation _; + + game::VariableValue value{}; + if (!safe_execution::get_entity_field(entref, id, &value)) + { + throw std::runtime_error("Failed to get value for field '" + field + "'"); + } + + const auto __ = gsl::finally([value]() + { + game::RemoveRefToValue(value.type, value.u); + }); + + return value; + } + + // Add custom fields + return get_custom_field(entity, field); + } +} diff --git a/src/client/game/scripting/execution.hpp b/src/client/game/scripting/execution.hpp new file mode 100644 index 00000000..7fff1840 --- /dev/null +++ b/src/client/game/scripting/execution.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "game/game.hpp" +#include "entity.hpp" +#include "script_value.hpp" + +namespace scripting +{ + script_value call_function(const std::string& name, const std::vector& arguments); + script_value call_function(const std::string& name, const entity& entity, + const std::vector& arguments); + + template + T call(const std::string& name, const std::vector& arguments = {}); + + template <> + script_value call(const std::string& name, const std::vector& arguments); + + template + T call(const std::string& name, const std::vector& arguments) + { + return call(name, arguments).as(); + } + + script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector& arguments); + const char* get_function_pos(const std::string& filename, const std::string& function); + script_value call_script_function(const entity& entity, const std::string& filename, + const std::string& function, const std::vector& arguments); + + void clear_entity_fields(const entity& entity); + void clear_custom_fields(); + + void set_entity_field(const entity& entity, const std::string& field, const script_value& value); + script_value get_entity_field(const entity& entity, const std::string& field); + + void notify(const entity& entity, const std::string& event, const std::vector& arguments); +} diff --git a/src/client/game/scripting/function_tables.cpp b/src/client/game/scripting/function_tables.cpp new file mode 100644 index 00000000..6eac3d97 --- /dev/null +++ b/src/client/game/scripting/function_tables.cpp @@ -0,0 +1,1535 @@ +#include + +// This file has been generated. +// Do not touch! + +namespace scripting +{ + std::unordered_map function_map = + { + {"precacheturret", 0}, + {"getweaponarray", 1}, + {"getnumparam", 15}, + {"getnumparam", 16}, + {"spawnturret", 23}, + {"canspawnturret", 24}, + {"assertexcmd", 25}, + {"badplace_delete", 30}, + {"badplace_cylinder", 31}, + {"badplace_arc", 32}, + {"badplace_brush", 33}, + {"assertexcmd0", 44}, + {"isdefined", 46}, + {"isvalidmissile", 47}, + {"isstring", 48}, + {"setomnvar", 49}, + {"getomnvar", 50}, + {"setdvar", 51}, + {"setdynamicdvar", 52}, + {"setdvarifuninitialized", 53}, + {"setdevdvar", 54}, + {"setdevdvarifuninitialized", 55}, + {"getdvar", 56}, + {"getdvarint", 57}, + {"getdvarfloat", 58}, + {"getdvarvector", 59}, + {"gettime", 60}, + {"getutc", 61}, + {"getradiometricunit", 62}, + {"getentbynum", 63}, + {"getweaponmodel", 64}, + {"setsunlight", 68}, + {"resetsunlight", 69}, + {"getweapondisplayname", 92}, + {"getweaponbasename", 93}, + {"getweaponattachments", 94}, + {"getweaponattachmentdisplaynames", 95}, + {"getweaponcamoname", 96}, + {"getweaponreticlename", 97}, + {"getanimlength", 98}, + {"animhasnotetrack", 99}, + {"getnotetracktimes", 100}, + {"spawn", 101}, + {"spawnloopingsound", 103}, + {"bullettrace", 104}, + {"getstartorigin", 108}, + {"getstartangles", 109}, + {"magicgrenademanual", 112}, + {"sub_140311ad0", 117}, + {"sub_140311d80", 118}, + {"sub_140311d90", 119}, + {"sub_140311df0", 120}, + {"sub_140311ef0", 121}, + {"sub_140311f50", 122}, + {"sub_14031fb60", 127}, + {"bullettracepassed", 138}, + {"sighttracepassed", 139}, + {"physicstrace", 140}, + {"playerphysicstrace", 141}, + {"getgroundposition", 142}, + {"getmovedelta", 143}, + {"getangledelta", 144}, + {"getnorthyaw", 145}, + {"setnorthyaw", 172}, + {"setslowmotion", 173}, + {"randomint", 174}, + {"randomfloat", 175}, + {"randomintrange", 176}, + {"randomfloatrange", 177}, + {"sin", 178}, + {"cos", 179}, + {"tan", 180}, + {"asin", 181}, + {"acos", 182}, + {"atan", 183}, + {"castint", 184}, + {"castfloat", 185}, + {"abs", 186}, + {"min", 187}, + {"getnode", 191}, + {"getnodearray", 192}, + {"getallnodes", 193}, + {"getnodesinradius", 194}, + {"getnodesinradiussorted", 195}, + {"getclosestnodeinsight", 196}, + {"isarray", 202}, + {"isai", 203}, + {"getindexforluincstring", 204}, + {"issentient", 205}, + {"max", 221}, + {"floor", 222}, + {"ceil", 223}, + {"exp", 224}, + {"log", 225}, + {"sqrt", 226}, + {"squared", 227}, + {"clamp", 228}, + {"angleclamp360", 229}, + {"angleclamp180", 230}, + {"vectorfromlinetopoint", 231}, + {"pointonsegmentnearesttopoint", 232}, + {"distance", 233}, + {"distance2d", 234}, + {"distancesquared", 235}, + {"length", 236}, + {"length2d", 237}, + {"lengthsquared", 238}, + {"length2dsquared", 239}, + {"closer", 240}, + {"vectordot", 241}, + {"vectorcross", 242}, + {"axistoangles", 243}, + {"visionsetthermal", 244}, + {"visionsetpain", 245}, + {"startservermigration", 246}, + {"setac130ambience", 247}, + {"getmapcustomfield", 248}, + {"spawnsighttrace", 249}, + {"incrementcounter", 250}, + {"getcountertotal", 251}, + {"createthreatbiasgroup", 258}, + {"threatbiasgroupexists", 259}, + {"getthreatbias", 260}, + {"setthreatbias", 261}, + {"setthreatbiasagainstall", 262}, + {"setignoremegroup", 263}, + {"isenemyteam", 264}, + {"vectornormalize", 271}, + {"vectortoangles", 272}, + {"vectortoyaw", 273}, + {"vectorlerp", 274}, + {"anglestoup", 275}, + {"anglestoright", 276}, + {"anglestoforward", 277}, + {"anglesdelta", 278}, + {"combineangles", 279}, + {"transformmove", 280}, + {"rotatevector", 281}, + {"rotatepointaroundvector", 282}, + {"issubstr", 283}, + {"isendstr", 284}, + {"getsubstr", 285}, + {"tolower", 286}, + {"strtok", 287}, + {"stricmp", 288}, + {"ambientplay", 289}, + {"getuavstrengthmax", 290}, + {"getuavstrengthlevelneutral", 291}, + {"getuavstrengthlevelshowenemyfastsweep", 292}, + {"getuavstrengthlevelshowenemydirectional", 293}, + {"blockteamradar", 294}, + {"unblockteamradar", 295}, + {"isteamradarblocked", 296}, + {"sub_140328710", 297}, + {"setmatchdata", 298}, + {"getmatchdata", 299}, + {"sendmatchdata", 300}, + {"clearmatchdata", 301}, + {"setmatchdatadef", 302}, + {"setmatchclientip", 303}, + {"setmatchdataid", 304}, + {"setclientmatchdata", 305}, + {"getclientmatchdata", 306}, + {"setclientmatchdatadef", 307}, + {"sendclientmatchdata", 308}, + {"getbuildversion", 309}, + {"getbuildnumber", 310}, + {"getsystemtime", 311}, + {"getmatchrulesdata", 312}, + {"isusingmatchrulesdata", 313}, + {"kickplayer", 314}, + {"issplitscreen", 315}, + {"setmapcenter", 316}, + {"setgameendtime", 317}, + {"visionsetnaked", 318}, + {"visionsetnight", 319}, + {"visionsetmissilecam", 320}, + {"ambientstop", 321}, + {"precachemodel", 322}, + {"precacheshellshock", 323}, + {"precacheitem", 324}, + {"precachematerial", 325}, + {"precachestring", 326}, + {"precachemenu", 327}, + {"precacherumble", 328}, + {"precachelocationselector", 329}, + {"precacheleaderboards", 330}, + {"loadfx", 331}, + {"playfx", 332}, + {"playfxontag", 333}, + {"stopfxontag", 334}, + {"killfxontag", 335}, + {"playloopedfx", 336}, + {"spawnfx", 337}, + {"triggerfx", 338}, + {"playfxontagforclients", 339}, + {"sub_1403326a0", 340}, + {"sub_14031be80", 341}, + {"setwinningteam", 342}, + {"announcement", 343}, + {"clientannouncement", 344}, + {"setteammode", 345}, + {"getteamscore", 346}, + {"setteamscore", 347}, + {"setclientnamemode", 348}, + {"updateclientnames", 349}, + {"getteamplayersalive", 350}, + {"worldentnumber", 352}, + {"obituary", 353}, + {"positionwouldtelefrag", 354}, + {"canspawn", 355}, + {"getstarttime", 356}, + {"precacheheadicon", 357}, + {"precacheminimapicon", 358}, + {"precachempanim", 359}, + {"maprestart", 360}, + {"exitlevel", 361}, + {"addtestclient", 362}, + {"addagent", 363}, + {"allclientsprint", 365}, + {"clientprint", 366}, + {"mapexists", 367}, + {"isvalidgametype", 368}, + {"setplayerteamrank", 370}, + {"setteamradar", 372}, + {"getteamradar", 373}, + {"setteamradarstrength", 374}, + {"getteamradarstrength", 375}, + {"getuavstrengthmin", 376}, + {"physicsexplosionsphere", 377}, + {"physicsexplosioncylinder", 378}, + {"physicsradiusjolt", 379}, + {"physicsradiusjitter", 380}, + {"setexpfog", 381}, + {"setexpfogext", 382}, + {"setexpfogdvarsonly", 383}, + {"setexpfogextdvarsonly", 384}, + {"setatmosfog", 385}, + {"setatmosfogdvarsonly", 386}, + {"isexplosivedamagemod", 387}, + {"radiusdamage", 388}, + {"setplayerignoreradiusdamage", 389}, + {"glassradiusdamage", 390}, + {"earthquake", 391}, + {"getnumparts", 392}, + {"objective_onentity", 393}, + {"objective_onentitywithrotation", 394}, + {"objective_team", 395}, + {"objective_player", 396}, + {"objective_playerteam", 397}, + {"objective_playerenemyteam", 398}, + {"objective_playermask_hidefromall", 399}, + {"objective_playermask_hidefrom", 400}, + {"objective_playermask_showtoall", 401}, + {"objective_playermask_showto", 402}, + {"iprintln", 403}, + {"iprintlnbold", 404}, + {"getent", 406}, + {"getentarray", 407}, + {"getspawnarray", 408}, + {"spawnplane", 409}, + {"addstruct", 410}, + {"spawnhelicopter", 411}, + {"isalive", 412}, + {"isspawner", 413}, + {"missilecreateattractorent", 414}, + {"missilecreateattractororigin", 415}, + {"missilecreaterepulsorent", 416}, + {"missilecreaterepulsororigin", 417}, + {"missiledeleteattractor", 418}, + {"playsoundatpos", 419}, + {"newhudelem", 420}, + {"newclienthudelem", 421}, + {"newteamhudelem", 422}, + {"resettimeout", 423}, + {"isplayer", 424}, + {"isplayernumber", 425}, + {"getpartname", 426}, + {"weaponfiretime", 427}, + {"weaponclipsize", 428}, + {"weaponisauto", 429}, + {"weaponissemiauto", 430}, + {"weaponisboltaction", 431}, + {"weaponinheritsperks", 432}, + {"weaponburstcount", 433}, + {"weapontype", 434}, + {"weaponclass", 435}, + {"getnextarraykey", 436}, + {"sortbydistance", 437}, + {"tablelookup", 438}, + {"tablelookupbyrow", 439}, + {"tablelookupistring", 440}, + {"tablelookupistringbyrow", 441}, + {"tablelookuprownum", 442}, + {"tableexists", 443}, + {"getmissileowner", 444}, + {"magicbullet", 445}, + {"getweaponflashtagname", 446}, + {"averagepoint", 447}, + {"averagenormal", 448}, + {"getspawnerarray", 449}, + {"playrumbleonposition", 450}, + {"playrumblelooponposition", 451}, + {"stopallrumbles", 452}, + {"soundexists", 453}, + {"setminimap", 460}, + {"setthermalbodymaterial", 461}, + {"getarraykeys", 462}, + {"getfirstarraykey", 463}, + {"getglass", 464}, + {"getglassarray", 465}, + {"getglassorigin", 466}, + {"isglassdestroyed", 467}, + {"destroyglass", 468}, + {"deleteglass", 469}, + {"getentchannelscount", 470}, + {"getentchannelname", 471}, + {"objective_add", 472}, + {"objective_delete", 473}, + {"objective_state", 474}, + {"objective_icon", 475}, + {"objective_position", 476}, + {"objective_current", 477}, + {"weaponinventorytype", 478}, + {"weaponstartammo", 479}, + {"weaponmaxammo", 480}, + {"weaponaltweaponname", 481}, + {"isweaponcliponly", 482}, + {"sub_14030dfc0", 483}, + {"sub_14030e400", 484}, + {"weaponhasthermalscope", 485}, + {"getvehiclenode", 486}, + {"getvehiclenodearray", 487}, + {"getallvehiclenodes", 488}, + {"getactivecount", 489}, + {"precache", 490}, + {"spawnvehicle", 491}, + {"getarray", 492}, + {"pow", 493}, + {"atan2", 494}, + {"botgetmemoryevents", 495}, + {"botautoconnectenabled", 496}, + {"botzonegetcount", 497}, + {"botzonesetteam", 498}, + {"botzonenearestcount", 499}, + {"botmemoryflags", 500}, + {"botflagmemoryevents", 501}, + {"botzonegetindoorpercent", 502}, + {"botsentientswap", 503}, + {"isbot", 504}, + {"isagent", 505}, + {"getmaxagents", 506}, + {"botgetclosestnavigablepoint", 508}, + {"getnodesintrigger", 509}, + {"nodesvisible", 510}, + {"getnodesonpath", 511}, + {"getzonecount", 512}, + {"getzonenearest", 513}, + {"getzonenodes", 514}, + {"getzonepath", 515}, + {"getzoneorigin", 516}, + {"getnodezone", 517}, + {"getzonenodesbydist", 518}, + {"getzonenodeforindex", 519}, + {"getweaponexplosionradius", 520}, + {"nodeexposedtosky", 523}, + {"findentrances", 524}, + {"badplace_global", 525}, + {"getpathdist", 526}, + {"getlinkednodes", 527}, + {"disconnectnodepair", 528}, + {"connectnodepair", 529}, + {"precachesound", 533}, + {"distance2dsquared", 543}, + {"getangledelta3d", 544}, + {"activateclientexploder", 545}, + {"trajectorycalculateinitialvelocity", 546}, + {"trajectorycalculateminimumvelocity", 547}, + {"trajectorycalculateexitangle", 548}, + {"trajectoryestimatedesiredinairtime", 549}, + {"trajectorycomputedeltaheightattime", 550}, + {"trajectorycanattemptaccuratejump", 551}, + {"ispointinvolume", 553}, + {"getscriptablearray", 560}, + {"clearfog", 561}, + {"setleveldopplerpreset", 562}, + {"isusinghdr", 564}, + {"sub_140321c40", 565}, + {"sub_140311a40", 567}, + {"sub_14030ec50", 568}, + {"sub_14030f050", 569}, + {"sub_14030f340", 570}, + {"sub_14030f550", 571}, + {"sub_14030f710", 572}, + {"sub_1403295e0", 573}, + {"sub_140322690", 574}, + {"sub_140329600", 575}, + {"sub_14031a690", 580}, + {"sub_1403163c0", 581}, + {"anglestoaxis", 582}, + {"invertangles", 587}, + {"rotatevectorinverted", 588}, + {"calculatestartorientation", 589}, + {"droptoground", 590}, + {"precachelaser", 592}, + {"getcsplinecount", 593}, + {"getcsplinepointcount", 594}, + {"getcsplinelength", 595}, + {"getcsplinepointid", 596}, + {"getcsplinepointlabel", 597}, + {"getcsplinepointtension", 598}, + {"getcsplinepointposition", 599}, + {"getcsplinepointcorridordims", 600}, + {"getcsplinepointtangent", 601}, + {"getcsplinepointdisttonextpoint", 602}, + {"calccsplineposition", 603}, + {"calccsplinetangent", 604}, + {"calccsplinecorridor", 605}, + {"setnojipscore", 606}, + {"setnojiptime", 607}, + {"getpredictedentityposition", 608}, + {"queuedialog", 615}, + {"triggerportableradarping", 622}, + {"botgetteamlimit", 624}, + {"spawnfxforclient", 625}, + {"botgetteamdifficulty", 626}, + {"loadluifile", 632}, + {"isdedicatedserver", 633}, + {"getplaylistversion", 634}, + {"getplaylistid", 635}, + {"getactiveclientcount", 636}, + {"issquadsmode", 637}, + {"visionsetpostapply", 639}, + {"addbot", 640}, + {"sub_140310ec0", 641}, + {"sub_14031bae0", 642}, + {"sub_14031c2b0", 643}, + {"isalliedsentient", 644}, + {"istestclient", 645}, + {"sub_1402d2850", 646}, + {"sub_140311ff0", 648}, + {"sub_140312040", 649}, + {"sub_140311100", 651}, + {"sub_140314c70", 652}, + {"sub_14030d340", 653}, + {"sub_14030da60", 654}, + {"sub_14030e5c0", 655}, + {"sub_14031fda0", 657}, + {"sub_140317140", 658}, + {"isremovedentity", 659}, + {"tablegetrowcount", 660}, + {"tablegetcolumncount", 661}, + {"batteryusepershot", 662}, + {"batteryreqtouse", 663}, + {"sub_14030e700", 664}, + {"getentityweaponname", 666}, + {"sub_14031fc20", 667}, + {"deployriotshield", 668}, + {"validatecostume", 669}, + {"randomcostume", 670}, + {"shootblank", 671}, + {"debugstringtostring", 673}, + {"sub_140319680", 675}, + {"playcinematicforall", 679}, + {"preloadcinematicforall", 680}, + {"stopcinematicforall", 681}, + {"capsuletracepassed", 682}, + {"sub_14031ca40", 683}, + {"sub_14031e1f0", 684}, + {"sub_140321880", 685}, + {"lootservicestarttrackingplaytime", 687}, + {"sub_1403297b0", 688}, + {"lootservicevalidateplaytime", 689}, + {"recordbreadcrumbdataforplayer", 690}, + {"sub_140317df0", 691}, + {"sysprint", 693}, + {"sub_140337920", 694}, + {"sub_140321ae0", 697}, + {"isonlinegame", 698}, + {"issystemlink", 699}, + {"getstanceandmotionstateforplayer", 701}, + {"sub_1402d3540", 702}, + {"sub_1402d35b0", 703}, + {"sub_140332a70", 704}, + {"sub_140332ae0", 705}, + {"getplaylistname", 706}, + {"getlocaltime", 707}, + {"sub_14032c820", 708}, + {"getchallengeid", 710}, + {"nodegetremotemissilename", 711}, + {"nodehasremotemissileset", 712}, + {"remotemissileenttracetooriginpassed", 713}, + {"bombingruntracepassed", 714}, + {"handlepickupdeployedriotshield", 716}, + {"sub_14032c6b0", 717}, + {"getcostumefromtable", 718}, + {"sub_1402d3460", 720}, + {"getchallenerewarditem", 722}, + {"setentplayerxuidforemblem", 723}, + {"resetentplayerxuidforemblems", 724}, + {"nodesetremotemissilename", 725}, + {"sub_14031aa80", 726}, + {"sub_14031ead0", 727}, + {"iszombie", 728}, + {"sub_14031b670", 729}, + {"sub_14031d3f0", 730}, + {"sub_14031e670", 731}, + {"getactiveplayerlist", 732}, + {"sub_140319200", 733}, + {"sub_140331e00", 734}, + }; + + std::unordered_map method_map = + { + {"thermaldrawdisable", 32768}, + {"heli_setdamagestage", 32770}, + {"playsoundtoteam", 32771}, + {"playsoundtoplayer", 32772}, + {"playerhide", 32773}, + {"playershow", 32774}, + {"showtoplayer", 32775}, + {"threatdetectedtoplayer", 32776}, + {"clearthreatdetected", 32777}, + {"enableplayeruse", 32778}, + {"disableplayeruse", 32779}, + {"enableammogeneration", 32780}, + {"disableammogeneration", 32781}, + {"makescrambler", 32782}, + {"makeportableradar", 32783}, + {"clearscrambler", 32784}, + {"clearportableradar", 32785}, + {"placespawnpoint", 32786}, + {"setteamfortrigger", 32787}, + {"clientclaimtrigger", 32788}, + {"clientreleasetrigger", 32789}, + {"releaseclaimedtrigger", 32790}, + {"isusingonlinedataoffline", 32791}, + {"getrestedtime", 32792}, + {"sendleaderboards", 32793}, + {"isonladder", 32794}, + {"getcorpseanim", 32795}, + {"playerforcedeathanim", 32796}, + {"attach", 32797}, + {"startragdoll", 32803}, + {"thermaldrawenable", 32809}, + {"detach", 32810}, + {"detachall", 32811}, + {"getattachsize", 32812}, + {"getattachmodelname", 32813}, + {"getattachtagname", 32814}, + {"gethighestnodestance", 32820}, + {"doesnodeallowstance", 32821}, + {"getlightcolor", 32835}, + {"setlightcolor", 32836}, + {"getattachignorecollision", 32839}, + {"hidepart", 32840}, + {"hidepartallinstances", 32841}, + {"hideallparts", 32842}, + {"showpart", 32843}, + {"showallparts", 32844}, + {"linkto", 32845}, + {"linktoblendtotag", 32846}, + {"unlink", 32847}, + {"setnormalhealth", 32848}, + {"dodamage", 32849}, + {"show", 32851}, + {"hide", 32852}, + {"disconnectpaths", 32855}, + {"connectpaths", 32856}, + {"disconnectnode", 32857}, + {"connectnode", 32858}, + {"digitaldistortsetparams", 32868}, + {"setmode", 32869}, + {"getmode", 32870}, + {"islinked", 32872}, + {"enablelinkto", 32873}, + {"setpitch", 32876}, + {"scalepitch", 32877}, + {"setvolume", 32878}, + {"scalevolume", 32879}, + {"playsound", 32884}, + {"playloopsound", 32885}, + {"getnormalhealth", 32891}, + {"playerlinkto", 32892}, + {"playerlinktodelta", 32893}, + {"playerlinkweaponviewtodelta", 32894}, + {"playerlinktoabsolute", 32895}, + {"playerlinktoblend", 32896}, + {"playerlinkedoffsetenable", 32897}, + {"setwaypointedgestyle_secondaryarrow", 32898}, + {"setwaypointiconoffscreenonly", 32899}, + {"fadeovertime", 32900}, + {"scaleovertime", 32901}, + {"moveovertime", 32902}, + {"reset", 32903}, + {"destroy", 32904}, + {"setpulsefx", 32905}, + {"setplayernamestring", 32906}, + {"changefontscaleovertime", 32907}, + {"playersetgroundreferenceent", 32913}, + {"dontinterpolate", 32914}, + {"getorigin", 32917}, + {"useby", 32921}, + {"playsoundasmaster", 32922}, + {"playerlinkedoffsetdisable", 32927}, + {"playerlinkedsetviewznear", 32928}, + {"playerlinkedsetusebaseangleforviewclamp", 32929}, + {"lerpviewangleclamp", 32930}, + {"geteye", 32936}, + {"istouching", 32937}, + {"getistouchingentities", 32938}, + {"stoploopsound", 32939}, + {"stopsounds", 32940}, + {"playrumbleonentity", 32941}, + {"playrumblelooponentity", 32942}, + {"stoprumble", 32943}, + {"delete", 32944}, + {"setmodel", 32945}, + {"laseron", 32946}, + {"laseroff", 32947}, + {"thermalvisionon", 32950}, + {"thermalvisionoff", 32951}, + {"thermalvisionfofoverlayon", 32952}, + {"thermalvisionfofoverlayoff", 32953}, + {"autospotoverlayon", 32954}, + {"autospotoverlayoff", 32955}, + {"seteyesonuplinkenabled", 32956}, + {"setcontents", 32958}, + {"makeusable", 32959}, + {"makeunusable", 32960}, + {"makeglobalusable", 32961}, + {"makeglobalunusable", 32962}, + {"settext", 32970}, + {"setmaterial", 32972}, + {"settargetent", 32973}, + {"cleartargetent", 32974}, + {"settimer", 32975}, + {"settimerup", 32976}, + {"settimerstatic", 32977}, + {"settenthstimer", 32978}, + {"settenthstimerup", 32979}, + {"settenthstimerstatic", 32980}, + {"setclock", 32981}, + {"setclockup", 32982}, + {"setvalue", 32983}, + {"setwaypoint", 32984}, + {"setwaypointedgestyle_rotatingicon", 32985}, + {"setcursorhint", 32986}, + {"sethintstring", 32987}, + {"setsecondaryhintstring", 32988}, + {"forceusehinton", 32989}, + {"forceusehintoff", 32990}, + {"makesoft", 32991}, + {"makehard", 32992}, + {"entitywillneverchange", 32993}, + {"startfiring", 32994}, + {"stopfiring", 32995}, + {"isfiringturret", 32996}, + {"startbarrelspin", 32997}, + {"stopbarrelspin", 32998}, + {"getbarrelspinrate", 32999}, + {"remotecontrolturret", 33000}, + {"remotecontrolturretoff", 33001}, + {"shootturret", 33002}, + {"getturretowner", 33003}, + {"giveachievement", 33017}, + {"sub_1402ddb00", 33022}, + {"sub_1402ddcc0", 33023}, + {"setsentryowner", 33027}, + {"setsentrycarrier", 33028}, + {"setturretminimapvisible", 33029}, + {"settargetentity", 33030}, + {"snaptotargetentity", 33031}, + {"cleartargetentity", 33032}, + {"getturrettarget", 33033}, + {"setplayerspread", 33034}, + {"setaispread", 33035}, + {"setsuppressiontime", 33036}, + {"allowstand", 33048}, + {"allowcrouch", 33049}, + {"allowprone", 33050}, + {"sub_1402dd9e0", 33051}, + {"isthrowinggrenade", 33068}, + {"isfiring", 33069}, + {"ismeleeing", 33070}, + {"allowmelee", 33072}, + {"allowfire", 33073}, + {"setconvergencetime", 33075}, + {"setconvergenceheightpercent", 33076}, + {"setturretteam", 33077}, + {"maketurretsolid", 33078}, + {"maketurretoperable", 33079}, + {"maketurretinoperable", 33080}, + {"makeentitysentient", 33081}, + {"freeentitysentient", 33082}, + {"setrightarc", 33109}, + {"setleftarc", 33110}, + {"settoparc", 33111}, + {"setbottomarc", 33112}, + {"setautorotationdelay", 33113}, + {"setdefaultdroppitch", 33114}, + {"restoredefaultdroppitch", 33115}, + {"turretfiredisable", 33116}, + {"getenemyinfo", 33125}, + {"getenemysqdist", 33141}, + {"getclosestenemysqdist", 33142}, + {"setthreatbiasgroup", 33143}, + {"getthreatbiasgroup", 33144}, + {"turretfireenable", 33145}, + {"setturretmodechangewait", 33146}, + {"usetriggerrequirelookat", 33147}, + {"getstance", 33148}, + {"setstance", 33149}, + {"itemweaponsetammo", 33150}, + {"getammocount", 33151}, + {"gettagorigin", 33152}, + {"gettagangles", 33153}, + {"shellshock", 33154}, + {"stunplayer", 33155}, + {"stopshellshock", 33156}, + {"fadeoutshellshock", 33157}, + {"setdepthoffield", 33158}, + {"setviewmodeldepthoffield", 33159}, + {"setmotionblurmovescale", 33160}, + {"getnegotiationstartnode", 33181}, + {"getnegotiationendnode", 33182}, + {"getnegotiationnextnode", 33183}, + {"setmotionblurturnscale", 33197}, + {"setmotionblurzoomscale", 33198}, + {"viewkick", 33199}, + {"localtoworldcoords", 33200}, + {"getentitynumber", 33201}, + {"getentityvelocity", 33202}, + {"enablegrenadetouchdamage", 33203}, + {"disablegrenadetouchdamage", 33204}, + {"enableaimassist", 33205}, + {"lastknowntime", 33216}, + {"lastknownpos", 33217}, + {"disableaimassist", 33236}, + {"entityradiusdamage", 33237}, + {"detonate", 33238}, + {"damageconetrace", 33239}, + {"sightconetrace", 33240}, + {"missilesettargetent", 33241}, + {"missilesettargetpos", 33242}, + {"missilecleartarget", 33243}, + {"missilesetflightmodedirect", 33244}, + {"missilesetflightmodetop", 33245}, + {"getlightintensity", 33246}, + {"setlightintensity", 33247}, + {"isragdoll", 33248}, + {"setmovespeedscale", 33249}, + {"cameralinkto", 33250}, + {"cameraunlink", 33251}, + {"controlslinkto", 33280}, + {"controlsunlink", 33281}, + {"makevehiclesolidcapsule", 33282}, + {"makevehiclesolidsphere", 33284}, + {"remotecontrolvehicle", 33286}, + {"remotecontrolvehicleoff", 33287}, + {"isfiringvehicleturret", 33288}, + {"remotecontrolvehicletarget", 33289}, + {"remotecontrolvehicletargetoff", 33290}, + {"drivevehicleandcontrolturret", 33291}, + {"drivevehicleandcontrolturretoff", 33292}, + {"getplayersetting", 33293}, + {"getlocalplayerprofiledata", 33294}, + {"setlocalplayerprofiledata", 33295}, + {"remotecamerasoundscapeon", 33296}, + {"remotecamerasoundscapeoff", 33297}, + {"setmotiontrackervisible", 33298}, + {"getmotiontrackervisible", 33299}, + {"worldpointinreticle_circle", 33300}, + {"worldpointinreticle_rect", 33301}, + {"getpointinbounds", 33302}, + {"transfermarkstonewscriptmodel", 33303}, + {"setwatersheeting", 33304}, + {"setweaponhudiconoverride", 33307}, + {"getweaponhudiconoverride", 33308}, + {"setempjammed", 33309}, + {"playersetexpfogext", 33310}, + {"playersetexpfog", 33311}, + {"playersetatmosfog", 33312}, + {"isitemunlocked", 33313}, + {"getplayerdata", 33314}, + {"getrankedplayerdata", 33315}, + {"getprivateplayerdata", 33316}, + {"getcoopplayerdata", 33317}, + {"getcommonplayerdata", 33318}, + {"vehicleturretcontroloff", 33319}, + {"isturretready", 33320}, + {"vehicledriveto", 33321}, + {"dospawn", 33322}, + {"isphysveh", 33323}, + {"crash", 33324}, + {"launch", 33325}, + {"disablecrashing", 33326}, + {"enablecrashing", 33327}, + {"setphysvehspeed", 33328}, + {"setconveyorbelt", 33329}, + {"freevehicle", 33330}, + {"setplayerdata", 33347}, + {"setrankedplayerdata", 33348}, + {"setprivateplayerdata", 33349}, + {"setcoopplayerdata", 33350}, + {"setcommonplayerdata", 33351}, + {"getcacplayerdata", 33352}, + {"setcacplayerdata", 33353}, + {"trackerupdate", 33354}, + {"pingplayer", 33355}, + {"buttonpressed", 33356}, + {"sayall", 33357}, + {"sayteam", 33358}, + {"setspawnweapon", 33359}, + {"dropitem", 33360}, + {"dropscavengerbag", 33361}, + {"setjitterparams", 33362}, + {"sethoverparams", 33363}, + {"joltbody", 33364}, + {"getwheelsurface", 33366}, + {"getvehicleowner", 33367}, + {"setvehiclelookattext", 33368}, + {"setvehicleteam", 33369}, + {"neargoalnotifydist", 33370}, + {"setvehgoalpos", 33371}, + {"setgoalyaw", 33372}, + {"cleargoalyaw", 33373}, + {"settargetyaw", 33374}, + {"cleartargetyaw", 33375}, + {"helisetgoal", 33376}, + {"setturrettargetvec", 33377}, + {"setturrettargetent", 33378}, + {"clearturrettargetent", 33379}, + {"canturrettargetpoint", 33380}, + {"setlookatent", 33381}, + {"clearlookatent", 33382}, + {"setweapon", 33383}, + {"fireweapon", 33384}, + {"vehicleturretcontrolon", 33385}, + {"finishplayerdamage", 33386}, + {"suicide", 33387}, + {"closeingamemenu", 33388}, + {"iclientprintln", 33389}, + {"iclientprintlnbold", 33390}, + {"spawn", 33391}, + {"setentertime", 33392}, + {"cloneplayer", 33393}, + {"istalking", 33394}, + {"allowspectateteam", 33395}, + {"forcespectatepov", 33396}, + {"getguid", 33397}, + {"physicslaunchserver", 33398}, + {"physicslaunchserveritem", 33399}, + {"clonebrushmodeltoscriptmodel", 33400}, + {"scriptmodelplayanim", 33401}, + {"scriptmodelclearanim", 33402}, + {"scriptmodelplayanimdeltamotion", 33403}, + {"teleport", 33404}, + {"attachpath", 33405}, + {"getattachpos", 33406}, + {"startpath", 33407}, + {"setswitchnode", 33408}, + {"setwaitspeed", 33409}, + {"finishdamage", 33410}, + {"setspeed", 33411}, + {"setspeedimmediate", 33412}, + {"rotatevehyaw", 33413}, + {"getspeed", 33414}, + {"getvehvelocity", 33415}, + {"getbodyvelocity", 33416}, + {"getsteering", 33417}, + {"getthrottle", 33418}, + {"turnengineoff", 33419}, + {"turnengineon", 33420}, + {"getgoalspeedmph", 33422}, + {"setacceleration", 33423}, + {"setdeceleration", 33424}, + {"resumespeed", 33425}, + {"setyawspeed", 33426}, + {"setyawspeedbyname", 33427}, + {"setmaxpitchroll", 33428}, + {"setairresitance", 33429}, + {"setturningability", 33430}, + {"getxuid", 33431}, + {"getucdidhigh", 33432}, + {"getucdidlow", 33433}, + {"getclanidhigh", 33434}, + {"getclanidlow", 33435}, + {"ishost", 33436}, + {"getspectatingplayer", 33437}, + {"predictstreampos", 33438}, + {"setrank", 33441}, + {"weaponlocknoclearance", 33443}, + {"visionsyncwithplayer", 33444}, + {"showhudsplash", 33445}, + {"setperk", 33446}, + {"hasperk", 33447}, + {"clearperks", 33448}, + {"unsetperk", 33449}, + {"registerparty", 33450}, + {"getfireteammembers", 33451}, + {"moveto", 33454}, + {"movex", 33455}, + {"movey", 33456}, + {"movez", 33457}, + {"gravitymove", 33458}, + {"moveslide", 33459}, + {"stopmoveslide", 33460}, + {"rotateto", 33461}, + {"rotatepitch", 33462}, + {"rotateyaw", 33463}, + {"rotateroll", 33464}, + {"addpitch", 33465}, + {"addyaw", 33466}, + {"addroll", 33467}, + {"vibrate", 33468}, + {"rotatevelocity", 33469}, + {"solid", 33470}, + {"notsolid", 33471}, + {"setcandamage", 33472}, + {"setcanradiusdamage", 33473}, + {"physicslaunchclient", 33474}, + {"setcarddisplayslot", 33477}, + {"kc_regweaponforfxremoval", 33478}, + {"laststandrevive", 33479}, + {"laststand", 33480}, + {"setspectatedefaults", 33481}, + {"getthirdpersoncrosshairoffset", 33482}, + {"disableweaponpickup", 33483}, + {"enableweaponpickup", 33484}, + {"issplitscreenplayer", 33485}, + {"getweaponslistoffhands", 33486}, + {"getweaponslistitems", 33487}, + {"getweaponslistexclusives", 33488}, + {"getweaponslist", 33489}, + {"canplayerplacesentry", 33490}, + {"canplayerplacetank", 33491}, + {"visionsetnakedforplayer", 33492}, + {"visionsetnightforplayer", 33493}, + {"visionsetmissilecamforplayer", 33494}, + {"visionsetthermalforplayer", 33495}, + {"visionsetpainforplayer", 33496}, + {"setblurforplayer", 33497}, + {"getplayerweaponmodel", 33498}, + {"getplayerknifemodel", 33499}, + {"notifyonplayercommand", 33501}, + {"canmantle", 33502}, + {"forcemantle", 33503}, + {"ismantling", 33504}, + {"playfx", 33505}, + {"playerrecoilscaleon", 33506}, + {"playerrecoilscaleoff", 33507}, + {"weaponlockstart", 33508}, + {"weaponlockfinalize", 33509}, + {"weaponlockfree", 33510}, + {"weaponlocktargettooclose", 33511}, + {"issplitscreenplayerprimary", 33512}, + {"markforeyeson", 33513}, + {"issighted", 33514}, + {"getsightedplayers", 33515}, + {"getplayerssightingme", 33516}, + {"getviewmodel", 33517}, + {"fragbuttonpressed", 33518}, + {"secondaryoffhandbuttonpressed", 33519}, + {"getcurrentweaponclipammo", 33520}, + {"setvelocity", 33521}, + {"getviewheight", 33522}, + {"getnormalizedmovement", 33523}, + {"playlocalsound", 33524}, + {"stoplocalsound", 33525}, + {"setweaponammoclip", 33526}, + {"setweaponammostock", 33527}, + {"getweaponammoclip", 33528}, + {"getweaponammostock", 33529}, + {"anyammoforweaponmodes", 33530}, + {"setclientomnvar", 33531}, + {"setclientdvar", 33532}, + {"setclientdvars", 33533}, + {"setclientspawnsighttraces", 33534}, + {"clientspawnsighttracepassed", 33535}, + {"allowads", 33536}, + {"allowjump", 33537}, + {"allowladder", 33538}, + {"allowmantle", 33539}, + {"allowsprint", 33540}, + {"setspreadoverride", 33541}, + {"resetspreadoverride", 33542}, + {"setaimspreadmovementscale", 33543}, + {"setactionslot", 33544}, + {"setviewkickscale", 33545}, + {"getviewkickscale", 33546}, + {"getweaponslistall", 33547}, + {"getweaponslistprimaries", 33548}, + {"getnormalizedcameramovement", 33549}, + {"giveweapon", 33550}, + {"takeweapon", 33551}, + {"takeallweapons", 33552}, + {"getcurrentweapon", 33553}, + {"getcurrentprimaryweapon", 33554}, + {"getcurrentoffhand", 33555}, + {"hasweapon", 33556}, + {"switchtoweapon", 33557}, + {"switchtoweaponimmediate", 33558}, + {"sub_1402e1d60", 33559}, + {"switchtooffhand", 33560}, + {"setoffhandsecondaryclass", 33561}, + {"getoffhandsecondaryclass", 33562}, + {"beginlocationselection", 33563}, + {"endlocationselection", 33564}, + {"disableweapons", 33565}, + {"enableweapons", 33566}, + {"disableoffhandweapons", 33567}, + {"enableoffhandweapons", 33568}, + {"disableweaponswitch", 33569}, + {"enableweaponswitch", 33570}, + {"openpopupmenu", 33571}, + {"openpopupmenunomouse", 33572}, + {"closepopupmenu", 33573}, + {"openmenu", 33574}, + {"closemenu", 33575}, + {"freezecontrols", 33577}, + {"disableusability", 33578}, + {"enableusability", 33579}, + {"setwhizbyspreads", 33580}, + {"setwhizbyradii", 33581}, + {"setchannelvolume", 33584}, + {"givestartammo", 33585}, + {"givemaxammo", 33586}, + {"getfractionstartammo", 33587}, + {"getfractionmaxammo", 33588}, + {"isdualwielding", 33589}, + {"isreloading", 33590}, + {"isswitchingweapon", 33591}, + {"setorigin", 33592}, + {"getvelocity", 33593}, + {"setangles", 33594}, + {"getangles", 33595}, + {"usebuttonpressed", 33596}, + {"attackbuttonpressed", 33597}, + {"adsbuttonpressed", 33598}, + {"meleebuttonpressed", 33599}, + {"playerads", 33600}, + {"isonground", 33601}, + {"isusingturret", 33602}, + {"setviewmodel", 33603}, + {"setoffhandprimaryclass", 33604}, + {"getoffhandprimaryclass", 33605}, + {"sub_14032dff0", 33610}, + {"sub_14032e040", 33611}, + {"enablemousesteer", 33612}, + {"setscriptmoverkillcam", 33613}, + {"usinggamepad", 33614}, + {"forcethirdpersonwhenfollowing", 33615}, + {"disableforcethirdpersonwhenfollowing", 33616}, + {"botsetflag", 33617}, + {"botsetstance", 33618}, + {"botsetscriptmove", 33619}, + {"botsetscriptgoal", 33620}, + {"botsetscriptgoalnode", 33621}, + {"botclearscriptgoal", 33622}, + {"botsetscriptenemy", 33623}, + {"botclearscriptenemy", 33624}, + {"botsetattacker", 33625}, + {"botgetscriptgoal", 33626}, + {"botgetscriptgoalradius", 33627}, + {"botgetscriptgoalyaw", 33628}, + {"botgetscriptgoaltype", 33629}, + {"botgetworldsize", 33631}, + {"botnodeavailable", 33632}, + {"botfindnoderandom", 33633}, + {"botmemoryevent", 33634}, + {"botnodepick", 33636}, + {"bothasscriptgoal", 33637}, + {"botgetpersonality", 33638}, + {"botthrowgrenade", 33639}, + {"botsetpersonality", 33641}, + {"botsetdifficulty", 33642}, + {"botgetdifficulty", 33643}, + {"botgetworldclosestedge", 33644}, + {"botlookatpoint", 33645}, + {"botpredictseepoint", 33646}, + {"botcanseeentity", 33647}, + {"botgetnodesonpath", 33648}, + {"botnodepickmultiple", 33649}, + {"botgetfovdot", 33651}, + {"botsetawareness", 33652}, + {"botpursuingscriptgoal", 33653}, + {"botgetscriptgoalnode", 33654}, + {"botgetimperfectenemyinfo", 33655}, + {"botsetpathingstyle", 33657}, + {"botsetdifficultysetting", 33658}, + {"botgetdifficultysetting", 33659}, + {"botgetpathdist", 33660}, + {"botisrandomized", 33661}, + {"botpressbutton", 33662}, + {"botclearbutton", 33663}, + {"botnodescoremultiple", 33664}, + {"getnodenumber", 33665}, + {"setclientowner", 33666}, + {"setotherent", 33667}, + {"setaisightlinevisible", 33668}, + {"setentityowner", 33669}, + {"nodeisdisconnected", 33670}, + {"getnearestnode", 33671}, + {"makeentitynomeleetarget", 33672}, + {"spawnagent", 33674}, + {"finishagentdamage", 33675}, + {"setagentattacker", 33676}, + {"cloneagent", 33677}, + {"agentcanseesentient", 33678}, + {"setagentwaypoint", 33679}, + {"setgoalpos", 33680}, + {"getgoalpos", 33681}, + {"setgoalnode", 33682}, + {"setgoalentity", 33683}, + {"setgoalradius", 33684}, + {"setanimscale", 33685}, + {"setorientmode", 33686}, + {"setanimmode", 33687}, + {"setphysicsmode", 33688}, + {"setclipmode", 33689}, + {"setmaxturnspeed", 33690}, + {"getmaxturnspeed", 33691}, + {"beginmelee", 33692}, + {"setscripted", 33693}, + {"dotrajectory", 33694}, + {"doanimlerp", 33695}, + {"setviewheight", 33696}, + {"claimnode", 33697}, + {"relinquishclaimednode", 33698}, + {"setradarping", 33699}, + {"visitfxent", 33700}, + {"sub_1402ef480", 33701}, + {"sub_1402ef4e0", 33702}, + {"sub_1402dd560", 33704}, + {"sub_1402dd590", 33705}, + {"allowhighjump", 33714}, + {"isjumping", 33715}, + {"ishighjumping", 33716}, + {"sub_140529a10", 33717}, + {"sub_140529a20", 33718}, + {"getbraggingright", 33719}, + {"getmodelfromentity", 33720}, + {"getweaponheatlevel", 33721}, + {"isweaponoverheated", 33722}, + {"isshiftbuttonpresseddown", 33723}, + {"sub_14052be00", 33724}, + {"sub_14052beb0", 33725}, + {"sub_14052bf30", 33726}, + {"lightsetforplayer", 33728}, + {"lightsetoverrideenableforplayer", 33729}, + {"lightsetoverridedisableforplayer", 33730}, + {"sub_140333c10", 33731}, + {"sub_140043710", 33732}, + {"sub_14052b420", 33733}, + {"sub_1402ddd70", 33734}, + {"setanimclass", 33744}, + {"enableanimstate", 33745}, + {"setanimstate", 33746}, + {"getanimentry", 33747}, + {"getanimentryname", 33748}, + {"getanimentryalias", 33749}, + {"getanimentrycount", 33750}, + {"issprinting", 33752}, + {"jumpbuttonpressed", 33758}, + {"rotateby", 33759}, + {"getlookaheaddir", 33760}, + {"getpathgoalpos", 33761}, + {"sub_140316940", 33762}, + {"setcorpsefalling", 33763}, + {"setsurfacetype", 33764}, + {"aiphysicstrace", 33765}, + {"aiphysicstracepassed", 33766}, + {"visionsetstage", 33770}, + {"linkwaypointtotargetwithoffset", 33771}, + {"getlinkedparent", 33772}, + {"getmovingplatformparent", 33773}, + {"setnameplatematerial", 33774}, + {"sub_140313d20", 33777}, + {"sub_1403131d0", 33778}, + {"makevehiclenotcollidewithplayers", 33779}, + {"setscriptablepartstate", 33782}, + {"stopsliding", 33783}, + {"sub_140316a60", 33784}, + {"setdronegoalpos", 33785}, + {"hudoutlineenable", 33786}, + {"hudoutlinedisable", 33787}, + {"worldpointtoscreenpos", 33792}, + {"botfirstavailablegrenade", 33795}, + {"emissiveblend", 33801}, + {"sub_1402e66d0", 33804}, + {"sub_1402e66e0", 33805}, + {"sub_1402e66f0", 33806}, + {"physicssetmaxlinvel", 33810}, + {"physicssetmaxangvel", 33811}, + {"physicsgetlinvel", 33812}, + {"physicsgetlinspeed", 33813}, + {"physicsgetangvel", 33814}, + {"physicsgetangspeed", 33815}, + {"disablemissileboosting", 33816}, + {"enablemissileboosting", 33817}, + {"canspawntestclient", 33818}, + {"spawntestclient", 33819}, + {"loadcustomizationplayerview", 33820}, + {"setgrenadethrowscale", 33821}, + {"setgrenadecookscale", 33822}, + {"setplanesplineid", 33823}, + {"hudoutlineenableforclient", 33824}, + {"hudoutlinedisableforclient", 33825}, + {"hudoutlineenableforclients", 33826}, + {"hudoutlinedisableforclients", 33827}, + {"turretsetbarrelspinenabled", 33828}, + {"hasloadedcustomizationplayerview", 33829}, + {"sub_140313420", 33830}, + {"sub_1403136f0", 33831}, + {"doanimrelative", 33832}, + {"getcorpseentity", 33836}, + {"logmatchdatalife", 33838}, + {"logmatchdatadeath", 33839}, + {"queuedialogforplayer", 33840}, + {"setmlgcameradefaults", 33841}, + {"ismlgspectator", 33842}, + {"disableautoreload", 33843}, + {"enableautoreload", 33844}, + {"getlinkedchildren", 33846}, + {"botpredictenemycampspots", 33847}, + {"playsoundonmovingent", 33848}, + {"cancelmantle", 33849}, + {"hasfemalecustomizationmodel", 33850}, + {"sub_1402e6bb0", 33851}, + {"setscriptabledamageowner", 33852}, + {"setfxkilldefondelete", 33853}, + {"sub_1402e1b80", 33856}, + {"sub_140310fb0", 33858}, + {"challengenotification", 33859}, + {"sub_140528300", 33860}, + {"sub_14052bff0", 33861}, + {"linktosynchronizedparent", 33862}, + {"getclientomnvar", 33863}, + {"getcacplayerdataforgroup", 33866}, + {"cloakingenable", 33867}, + {"cloakingdisable", 33868}, + {"getunnormalizedcameramovement", 33869}, + {"sub_14031edf0", 33870}, + {"isturretoverheated", 33871}, + {"sub_14052c170", 33872}, + {"sub_14052c190", 33873}, + {"sub_14052c1b0", 33874}, + {"sub_14052c1d0", 33875}, + {"sub_14052c0b0", 33878}, + {"sub_1402de140", 33879}, + {"getvieworigin", 33884}, + {"setweaponmodelvariant", 33885}, + {"ridevehicle", 33886}, + {"stopridingvehicle", 33887}, + {"autoboltmissileeffects", 33889}, + {"disablemissilestick", 33890}, + {"enablemissilestick", 33891}, + {"setmissileminimapvisible", 33892}, + {"isoffhandweaponreadytothrow", 33893}, + {"makecollidewithitemclip", 33895}, + {"visionsetpostapplyforplayer", 33897}, + {"setlookattarget", 33898}, + {"clearlookattarget", 33899}, + {"sub_14052c250", 33907}, + {"sub_14052c290", 33908}, + {"sub_14052c2f0", 33909}, + {"sub_14052c340", 33910}, + {"sub_14052c360", 33911}, + {"sub_14031c170", 33912}, + {"sub_14031c590", 33913}, + {"setclienttriggervisionset", 33914}, + {"sub_1402e41c0", 33917}, + {"sub_1402e43b0", 33918}, + {"sub_14052b4d0", 33919}, + {"sub_14052b550", 33920}, + {"showviewmodel", 33921}, + {"hideviewmodel", 33922}, + {"setpickupweapon", 33923}, + {"allowpowerslide", 33925}, + {"allowhighjumpdrop", 33926}, + {"sub_1404045e0", 33927}, + {"sub_1405297e0", 33928}, + {"sub_14052c200", 33929}, + {"clearentity", 33930}, + {"sub_140334a40", 33931}, + {"allowdodge", 33933}, + {"sub_140529860", 33934}, + {"setminimapvisible", 33935}, + {"sub_1402e0a90", 33936}, + {"sub_1402e0bc0", 33937}, + {"sub_1402e0cf0", 33938}, + {"setplayermech", 33940}, + {"setdamagecallbackon", 33941}, + {"finishentitydamage", 33942}, + {"designatefoftarget", 33946}, + {"sethintstringvisibleonlytoowner", 33947}, + {"notifyonplayercommandremove", 33948}, + {"sub_1402e3bf0", 33949}, + {"allowboostjump", 33950}, + {"batterydischargebegin", 33951}, + {"batterydischargeend", 33952}, + {"batterydischargeonce", 33953}, + {"batterygetcharge", 33954}, + {"batterysetcharge", 33955}, + {"batteryfullrecharge", 33956}, + {"batterygetsize", 33957}, + {"batterysetdischargescale", 33958}, + {"batterygetdischargerate", 33959}, + {"batteryisinuse", 33960}, + {"enablephysicaldepthoffieldscripting", 33961}, + {"disablephysicaldepthoffieldscripting", 33962}, + {"setphysicaldepthoffield", 33963}, + {"sub_140313860", 33964}, + {"sub_14052a560", 33965}, + {"sub_140321790", 33966}, + {"sub_140321a50", 33967}, + {"sub_1402dda50", 33968}, + {"sub_14052ac50", 33969}, + {"sub_14052ad50", 33970}, + {"setdemigod", 33971}, + {"sub_140310840", 33972}, + {"setcostumemodels", 33978}, + {"sub_140529e00", 33979}, + {"sub_140313510", 33980}, + {"scriptmodelpauseanim", 33981}, + {"digitaldistortsetmaterial", 33982}, + {"disableoffhandsecondaryweapons", 33983}, + {"enableoffhandsecondaryweapons", 33984}, + {"canplaceriotshield", 33985}, + {"setriotshieldfailhint", 33986}, + {"enabledetonate", 33987}, + {"getdetonateenabled", 33988}, + {"playergetuseent", 33989}, + {"refreshshieldmodels", 33990}, + {"sub_14052c3a0", 33991}, + {"locret_140406a70", 33993}, + {"getgravity", 33994}, + {"sub_140529560", 33997}, + {"sub_140529650", 33998}, + {"setcommonplayerdatareservedint", 33999}, + {"getrankedplayerdatareservedint", 34000}, + {"setrankedplayerdatareservedint", 34001}, + {"getcommonplayerdatareservedint", 34002}, + {"sub_1402e8a20", 34003}, + {"sub_14052c3c0", 34004}, + {"addsoundmutedevice", 34005}, + {"removesoundmutedevice", 34006}, + {"clientaddsoundsubmix", 34007}, + {"clientclearsoundsubmix", 34008}, + {"sub_140312520", 34009}, + {"sub_140312ba0", 34010}, + {"sub_140312bf0", 34011}, + {"sub_140312cb0", 34012}, + {"sub_140312df0", 34013}, + {"sub_140312ff0", 34014}, + {"isusingoffhand", 34016}, + {"physicsstop", 34017}, + {"sub_14031b9e0", 34018}, + {"sub_14031e3c0", 34023}, + {"sub_1402ef8a0", 34024}, + {"sub_14052c400", 34025}, + {"initwaterclienttrigger", 34026}, + {"getcurrentweaponmodelname", 34027}, + {"sub_14031f000", 34028}, + {"setignorefoliagesightingme", 34030}, + {"loadcostumemodels", 34031}, + {"sub_14030cd90", 34036}, + {"sub_14030b1c0", 34038}, + {"sub_140322450", 34039}, + {"iscloaked", 34040}, + {"sub_140528bc0", 34041}, + {"sub_140528cf0", 34042}, + {"sub_14031fb80", 34043}, + {"sub_140320180", 34044}, + {"selfieaccessselfievalidflaginplayerdef", 34045}, + {"selfieaccessselfiecustomassetsarestreamed", 34046}, + {"sub_14031ede0", 34048}, + {"selfiescreenshottaken", 34049}, + {"sub_14031f190", 34050}, + {"sub_140319de0", 34052}, + {"setmissilecoasting", 34053}, + {"setmlgspectator", 34054}, + {"gettotalmpxp", 34055}, + {"turretsetgroundaimentity", 34056}, + {"sub_140318610", 34057}, + {"sub_140317760", 34058}, + {"sub_14032e9a0", 34059}, + {"sub_140320830", 34060}, + {"sub_140329bc0", 34061}, + {"sub_14032e370", 34062}, + {"consumereinforcement", 34063}, + {"ghost", 34064}, + {"loadweapons", 34065}, + {"sub_1402e0e80", 34067}, + {"setwaypointiconfadeatcenter", 34068}, + {"setreinforcementhintstrings", 34069}, + {"playgoliathentryanim", 34070}, + {"playgoliathtoidleanim", 34071}, + {"sub_140312210", 34072}, + {"sub_140312280", 34073}, + {"sub_140321660", 34074}, + {"playlocalannouncersound", 34075}, + {"setmissilespecialclipmask", 34076}, + {"sub_140527c40", 34077}, + {"sub_140527c60", 34078}, + {"isdodging", 34079}, + {"ispowersliding", 34080}, + {"sub_140320ab0", 34081}, + {"getcurrentping", 34082}, + {"sub_1402eeb60", 34083}, + {"sub_140329390", 34084}, + {"gethordeplayerdata", 34085}, + {"sethordeplayerdata", 34086}, + {"sub_1402dcbc0", 34087}, + {"sub_14031a0b0", 34088}, + {"sub_1403198a0", 34089}, + {"sub_1403345e0", 34090}, + {"sub_1402e7d80", 34092}, + {"sub_140404f00", 34093}, + {"issplitscreenplayer2", 34095}, + {"setowneroriginal", 34096}, + {"getlinkedtagname", 34097}, + {"sub_14032de80", 34098}, + {"sub_14032dfb0", 34099}, + {"setwaypointaerialtargeting", 34100}, + {"worldweaponsloaded", 34101}, + {"sub_140320aa0", 34102}, + {"usetriggertouchcheckstance", 34103}, + {"onlystreamactiveweapon", 34104}, + {"precachekillcamiconforweapon", 34105}, + {"selfierequestupdate", 34106}, + {"getclanwarsbonus", 34107}, + {"sub_140406810", 34108}, + {"sub_1404051d0", 34109}, + {"sub_140406340", 34110}, + {"sub_1402e72a0", 34111}, + {"sub_140404c70", 34112}, + {"sub_1404065c0", 34113}, + {"sub_140405c60", 34114}, + {"sub_140406400", 34115}, + {"sub_140406230", 34116}, + {"sub_140406650", 34117}, + {"sub_1402e7de0", 34118}, + {"sub_140333550", 34119}, + {"sub_140403fe0", 34120}, + {"sub_140320360", 34121}, + {"canhighjump", 34122}, + {"setprestigemastery", 34123}, + {"sub_140403f50", 34124}, + {"sub_14030c7b0", 34125}, + {"sub_1403206b0", 34126}, + {"sub_140329960", 34127}, + {"sub_140328100", 34128}, + {"sub_140405990", 34129}, + {"sub_1402e70c0", 34130}, + {"sub_1403335f0", 34131}, + {"getcoopplayerdatareservedint", 34132}, + {"setcoopplayerdatareservedint", 34133}, + {"sub_140406b50", 34134}, + {"sub_1402de070", 34135}, + {"sub_1402e7e40", 34136}, + {"sub_140329ba0", 34137}, + {"sub_140405af0", 34138}, + {"sub_1402e7240", 34139}, + {"sub_14031a370", 34140}, + {"sub_140406970", 34141}, + {"sub_140405b60", 34142}, + {"sub_140334e10", 34143}, + {"sub_140320a90", 34144}, + {"sub_140406c00", 34145}, + {"sub_140328bf0", 34146}, + {"sub_1404053e0", 34147}, + {"sub_140406d20", 34148}, + {"sub_14032c900", 34149}, + {"sub_14032c9e0", 34150}, + {"sub_140044360", 34151}, + {"sub_140333680", 34152}, + {"sub_1402e7130", 34153}, + {"sub_1403294b0", 34154}, + {"sub_140320b40", 34155}, + {"sub_140333710", 34156}, + }; + + std::unordered_map token_map = + { + {"CodeCallback_BulletHitEntity", 180}, + {"CodeCallback_CodeEndGame", 181}, + {"CodeCallback_EntityDamage", 182}, + {"CodeCallback_EntityOutOfWorld", 183}, + {"CodeCallback_HostMigration", 185}, + {"CodeCallback_PartyMembers", 187}, + {"CodeCallback_PlayerConnect", 188}, + {"CodeCallback_PlayerDamage", 189}, + {"CodeCallback_PlayerDisconnect", 190}, + {"CodeCallback_PlayerGrenadeSuicide", 191}, + {"CodeCallback_PlayerKilled", 192}, + {"CodeCallback_PlayerLastStand", 193}, + {"CodeCallback_PlayerMigrated", 194}, + {"CodeCallback_StartGameType", 195}, + {"CodeCallback_VehicleDamage", 196}, + {"CreateStruct", 221}, + {"InitStructs", 522}, + {"main", 619}, + {"AbortLevel", 1727}, + {"callbackVoid", 6662}, + {"CodeCallback_GiveKillstreak", 8192}, + {"SetDefaultCallbacks", 32577}, + {"SetupCallbacks", 33531}, + {"SetupDamageFlags", 33542}, + {"struct", 36698}, + {"codescripts/delete", 0x053D}, + {"codescripts/struct", 0x053E}, + {"maps/mp/gametypes/_callbacksetup", 0x0540}, + {"codescripts/character", 0xA4E5}, + {"common_scripts/_artcommon", 42214}, + {"common_scripts/_bcs_location_trigs", 42215}, + {"common_scripts/_createfx", 42216}, + {"common_scripts/_createfxmenu", 42217}, + {"common_scripts/_destructible", 42218}, + {"common_scripts/_dynamic_world", 42219}, + {"maps/createart/mp_vlobby_room_art", 42735}, + {"maps/createart/mp_vlobby_room_fog", 42736}, + {"maps/createart/mp_vlobby_room_fog_hdr", 42737} + }; +} diff --git a/src/client/game/scripting/functions.cpp b/src/client/game/scripting/functions.cpp new file mode 100644 index 00000000..3452c767 --- /dev/null +++ b/src/client/game/scripting/functions.cpp @@ -0,0 +1,106 @@ +#include +#include "functions.hpp" + +#include + +namespace scripting +{ + namespace + { + std::unordered_map lowercase_map( + const std::unordered_map& old_map) + { + std::unordered_map new_map{}; + for (auto& entry : old_map) + { + new_map[utils::string::to_lower(entry.first)] = entry.second; + } + + return new_map; + } + + const std::unordered_map& get_methods() + { + static auto methods = lowercase_map(method_map); + return methods; + } + + const std::unordered_map& get_functions() + { + static auto function = lowercase_map(function_map); + return function; + } + + int find_function_index(const std::string& name, const bool prefer_global) + { + const auto target = utils::string::to_lower(name); + + const auto& primary_map = prefer_global + ? get_functions() + : get_methods(); + const auto& secondary_map = !prefer_global + ? get_functions() + : get_methods(); + + auto function_entry = primary_map.find(target); + if (function_entry != primary_map.end()) + { + return function_entry->second; + } + + function_entry = secondary_map.find(target); + if (function_entry != secondary_map.end()) + { + return function_entry->second; + } + + return -1; + } + + script_function get_function_by_index(const unsigned index) + { + static const auto function_table = SELECT_VALUE(0x149668F50, 0x147DD1850); + static const auto method_table = SELECT_VALUE(0x14966A670, 0x147DD2F50); + + if (index < 0x2DF) + { + return reinterpret_cast(function_table)[index]; + } + + return reinterpret_cast(method_table)[index - 0x8000]; + } + } + + std::string find_token(unsigned int id) + { + for (const auto& token : token_map) + { + if (token.second == id) + { + return token.first; + } + } + + return utils::string::va("_ID%i", id); + } + + unsigned int find_token_id(const std::string& name) + { + const auto result = token_map.find(name); + + if (result != token_map.end()) + { + return result->second; + } + + return 0; + } + + script_function find_function(const std::string& name, const bool prefer_global) + { + const auto index = find_function_index(name, prefer_global); + if (index < 0) return nullptr; + + return get_function_by_index(index); + } +} diff --git a/src/client/game/scripting/functions.hpp b/src/client/game/scripting/functions.hpp new file mode 100644 index 00000000..0422bcf7 --- /dev/null +++ b/src/client/game/scripting/functions.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + extern std::unordered_map method_map; + extern std::unordered_map function_map; + extern std::unordered_map token_map; + + using script_function = void(*)(game::scr_entref_t); + + std::string find_token(unsigned int id); + unsigned int find_token_id(const std::string& name); + + script_function find_function(const std::string& name, const bool prefer_global); +} diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp new file mode 100644 index 00000000..462a6628 --- /dev/null +++ b/src/client/game/scripting/lua/context.cpp @@ -0,0 +1,489 @@ +#include +#include "context.hpp" +#include "error.hpp" +#include "value_conversion.hpp" + +#include "../execution.hpp" +#include "../functions.hpp" + +#include "../../../component/command.hpp" +#include "../../../component/logfile.hpp" +#include "../../../component/scripting.hpp" + +#include + +namespace scripting::lua +{ + namespace + { + std::vector load_game_constants() + { + std::vector result{}; + + const auto constants = game::GScr_LoadConsts.get(); + + ud_t ud; + ud_init(&ud); + ud_set_mode(&ud, 64); + ud_set_pc(&ud, uint64_t(constants)); + ud_set_input_buffer(&ud, reinterpret_cast(constants), INT32_MAX); + + while (true) + { + ud_disassemble(&ud); + + if (ud_insn_mnemonic(&ud) == UD_Iret) + { + break; + } + + if (ud_insn_mnemonic(&ud) == UD_Imov) + { + const auto* operand = ud_insn_opr(&ud, 0); + if (operand && operand->type == UD_OP_REG && operand->base == UD_R_ECX) + { + operand = ud_insn_opr(&ud, 1); + if (operand && operand->type == UD_OP_IMM && (operand->base == UD_R_RAX || operand->base == UD_R_EAX)) + { + result.emplace_back(reinterpret_cast(0x1409C1CE0)[operand->lval.udword]); + } + } + } + + if (ud_insn_mnemonic(&ud) == UD_Ilea) + { + const auto* operand = ud_insn_opr(&ud, 0); + if (!operand || operand->type != UD_OP_REG || operand->base != UD_R_RCX) + { + continue; + } + + operand = ud_insn_opr(&ud, 1); + if (operand && operand->type == UD_OP_MEM && operand->base == UD_R_RIP) + { + auto* operand_ptr = reinterpret_cast(ud_insn_len(&ud) + ud_insn_off(&ud) + operand->lval. + sdword); + if (!utils::memory::is_bad_read_ptr(operand_ptr) && utils::memory::is_rdata_ptr(operand_ptr) && + strlen(operand_ptr) > 0) + { + result.emplace_back(operand_ptr); + } + } + } + + if (*reinterpret_cast(ud.pc) == 0xCC) break; // int 3 + } + + return result; + } + + const std::vector& get_game_constants() + { + static auto constants = load_game_constants(); + return constants; + } + + void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler) + { + state["level"] = entity{*game::levelEntityId}; + + auto vector_type = state.new_usertype("vector", sol::constructors()); + vector_type["x"] = sol::property(&vector::get_x, &vector::set_x); + vector_type["y"] = sol::property(&vector::get_y, &vector::set_y); + vector_type["z"] = sol::property(&vector::get_z, &vector::set_z); + + vector_type["r"] = sol::property(&vector::get_x, &vector::set_x); + vector_type["g"] = sol::property(&vector::get_y, &vector::set_y); + vector_type["b"] = sol::property(&vector::get_z, &vector::set_z); + + auto entity_type = state.new_usertype("entity"); + + for (const auto& func : method_map) + { + const auto name = utils::string::to_lower(func.first); + entity_type[name.data()] = [name](const entity& entity, const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, entity.call(name, arguments)); + }; + } + + for (const auto& constant : get_game_constants()) + { + entity_type[constant] = sol::property( + [constant](const entity& entity, const sol::this_state s) + { + return convert(s, entity.get(constant)); + }, + [constant](const entity& entity, const sol::this_state s, const sol::lua_value& value) + { + entity.set(constant, convert({s, value})); + }); + } + + entity_type["set"] = [](const entity& entity, const std::string& field, + const sol::lua_value& value) + { + entity.set(field, convert(value)); + }; + + entity_type["get"] = [](const entity& entity, const sol::this_state s, const std::string& field) + { + return convert(s, entity.get(field)); + }; + + entity_type["notify"] = [](const entity& entity, const sol::this_state s, const std::string& event, + sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + notify(entity, event, arguments); + }; + + entity_type["onnotify"] = [&handler](const entity& entity, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.entity = entity; + listener.event = event; + listener.is_volatile = false; + + return handler.add_event_listener(std::move(listener)); + }; + + entity_type["onnotifyonce"] = [&handler](const entity& entity, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.entity = entity; + listener.event = event; + listener.is_volatile = true; + + return handler.add_event_listener(std::move(listener)); + }; + + entity_type["call"] = [](const entity& entity, const sol::this_state s, const std::string& function, + sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, entity.call(function, arguments)); + }; + + entity_type[sol::meta_function::new_index] = [](const entity& entity, const std::string& field, + const sol::lua_value& value) + { + entity.set(field, convert(value)); + }; + + entity_type[sol::meta_function::index] = [](const entity& entity, const sol::this_state s, const std::string& field) + { + return convert(s, entity.get(field)); + }; + + entity_type["getstruct"] = [](const entity& entity, const sol::this_state s) + { + const auto id = entity.get_entity_id(); + return scripting::lua::entity_to_struct(s, id); + }; + + entity_type["struct"] = sol::property([](const entity& entity, const sol::this_state s) + { + const auto id = entity.get_entity_id(); + return scripting::lua::entity_to_struct(s, id); + }); + + entity_type["scriptcall"] = [](const entity& entity, const sol::this_state s, const std::string& filename, + const std::string function, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, call_script_function(entity, filename, function, arguments)); + }; + + struct game + { + }; + auto game_type = state.new_usertype("game_"); + state["game"] = game(); + + for (const auto& func : function_map) + { + const auto name = utils::string::to_lower(func.first); + game_type[name] = [name](const game&, const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, call(name, arguments)); + }; + } + + game_type["call"] = [](const game&, const sol::this_state s, const std::string& function, + sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, call(function, arguments)); + }; + + game_type["ontimeout"] = [&scheduler](const game&, const sol::protected_function& callback, + const long long milliseconds) + { + return scheduler.add(callback, milliseconds, true); + }; + + game_type["oninterval"] = [&scheduler](const game&, const sol::protected_function& callback, + const long long milliseconds) + { + return scheduler.add(callback, milliseconds, false); + }; + + game_type["executecommand"] = [](const game&, const std::string& command) + { + command::execute(command, false); + }; + + game_type["onplayerdamage"] = [](const game&, const sol::protected_function& callback) + { + logfile::add_player_damage_callback(callback); + }; + + game_type["onplayerkilled"] = [](const game&, const sol::protected_function& callback) + { + logfile::add_player_killed_callback(callback); + }; + + game_type["getgamevar"] = [](const sol::this_state s) + { + const auto id = *reinterpret_cast(0x14815DEB4); + const auto value = ::game::scr_VarGlob->childVariableValue[id]; + + ::game::VariableValue variable{}; + variable.type = value.type; + variable.u.uintValue = value.u.u.uintValue; + + return convert(s, variable); + }; + + game_type["getfunctions"] = [entity_type](const game&, const sol::this_state s, const std::string& filename) + { + if (scripting::script_function_table.find(filename) == scripting::script_function_table.end()) + { + throw std::runtime_error("File '" + filename + "' not found"); + } + + auto functions = sol::table::create(s.lua_state()); + + for (const auto& function : scripting::script_function_table[filename]) + { + functions[function.first] = sol::overload( + [filename, function](const entity& entity, const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + gsl::finally(&logfile::enable_vm_execute_hook); + logfile::disable_vm_execute_hook(); + + return convert(s, call_script_function(entity, filename, function.first, arguments)); + }, + [filename, function](const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + gsl::finally(&logfile::enable_vm_execute_hook); + logfile::disable_vm_execute_hook(); + + return convert(s, call_script_function(*::game::levelEntityId, filename, function.first, arguments)); + } + ); + } + + return functions; + }; + + game_type["scriptcall"] = [](const game&, const sol::this_state s, const std::string& filename, + const std::string function, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + gsl::finally(&logfile::enable_vm_execute_hook); + logfile::disable_vm_execute_hook(); + + return convert(s, call_script_function(*::game::levelEntityId, filename, function, arguments)); + }; + + game_type["detour"] = [](const game&, const sol::this_state s, const std::string& filename, + const std::string function_name, const sol::protected_function& function) + { + const auto pos = get_function_pos(filename, function_name); + logfile::vm_execute_hooks[pos] = function; + + auto detour = sol::table::create(function.lua_state()); + + detour["disable"] = [pos]() + { + logfile::vm_execute_hooks.erase(pos); + }; + + detour["enable"] = [pos, function]() + { + logfile::vm_execute_hooks[pos] = function; + }; + + detour["invoke"] = sol::overload( + [filename, function_name](const entity& entity, const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + gsl::finally(&logfile::enable_vm_execute_hook); + logfile::disable_vm_execute_hook(); + + return convert(s, call_script_function(entity, filename, function_name, arguments)); + }, + [filename, function_name](const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + gsl::finally(&logfile::enable_vm_execute_hook); + logfile::disable_vm_execute_hook(); + + return convert(s, call_script_function(*::game::levelEntityId, filename, function_name, arguments)); + } + ); + + return detour; + }; + } + } + + context::context(std::string folder) + : folder_(std::move(folder)) + , scheduler_(state_) + , event_handler_(state_) + + { + this->state_.open_libraries(sol::lib::base, + sol::lib::package, + sol::lib::io, + sol::lib::string, + sol::lib::os, + sol::lib::math, + sol::lib::table); + + this->state_["include"] = [this](const std::string& file) + { + this->load_script(file); + }; + + sol::function old_require = this->state_["require"]; + auto base_path = utils::string::replace(this->folder_, "/", ".") + "."; + this->state_["require"] = [base_path, old_require](const std::string& path) + { + return old_require(base_path + path); + }; + + this->state_["scriptdir"] = [this]() + { + return this->folder_; + }; + + setup_entity_type(this->state_, this->event_handler_, this->scheduler_); + + printf("Loading script '%s'\n", this->folder_.data()); + this->load_script("__init__"); + } + + context::~context() + { + this->collect_garbage(); + this->scheduler_.clear(); + this->event_handler_.clear(); + this->state_ = {}; + } + + void context::run_frame() + { + this->scheduler_.run_frame(); + this->collect_garbage(); + } + + void context::notify(const event& e) + { + this->scheduler_.dispatch(e); + this->event_handler_.dispatch(e); + } + + void context::collect_garbage() + { + this->state_.collect_garbage(); + } + + void context::load_script(const std::string& script) + { + if (!this->loaded_scripts_.emplace(script).second) + { + return; + } + + const auto file = (std::filesystem::path{this->folder_} / (script + ".lua")).generic_string(); + handle_error(this->state_.safe_script_file(file, &sol::script_pass_on_error)); + } +} diff --git a/src/client/game/scripting/lua/context.hpp b/src/client/game/scripting/lua/context.hpp new file mode 100644 index 00000000..79a5e905 --- /dev/null +++ b/src/client/game/scripting/lua/context.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "../event.hpp" + +#pragma warning(push) +#pragma warning(disable: 4702) + +#define SOL_ALL_SAFETIES_ON 1 +#define SOL_PRINT_ERRORS 0 +#include + +#pragma warning(pop) + +#include "scheduler.hpp" +#include "event_handler.hpp" + +namespace scripting::lua +{ + class context + { + public: + context(std::string folder); + ~context(); + + context(context&&) noexcept = delete; + context& operator=(context&&) noexcept = delete; + + context(const context&) = delete; + context& operator=(const context&) = delete; + + void run_frame(); + void notify(const event& e); + void collect_garbage(); + + private: + sol::state state_{}; + std::string folder_; + std::unordered_set loaded_scripts_; + + scheduler scheduler_; + event_handler event_handler_; + + void load_script(const std::string& script); + }; +} diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp new file mode 100644 index 00000000..351ad04b --- /dev/null +++ b/src/client/game/scripting/lua/engine.cpp @@ -0,0 +1,75 @@ +#include +#include "engine.hpp" +#include "context.hpp" + +#include "../execution.hpp" +#include "../../../component/logfile.hpp" +#include "../../../component/game_module.hpp" + +#include + +namespace scripting::lua::engine +{ + namespace + { + auto& get_scripts() + { + static std::vector> scripts{}; + return scripts; + } + + void load_scripts(const std::string& script_dir) + { + if (!utils::io::directory_exists(script_dir)) + { + return; + } + + const auto scripts = utils::io::list_files(script_dir); + + for (const auto& script : scripts) + { + if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua")) + { + get_scripts().push_back(std::make_unique(script)); + } + } + } + } + + void stop() + { + logfile::clear_callbacks(); + get_scripts().clear(); + } + + void start() + { + // No SP until there is a concept + if (game::environment::is_sp()) + { + return; + } + + stop(); + load_scripts(game_module::get_host_module().get_folder() + "/data/scripts/"); + load_scripts("h1-mod/scripts/"); + load_scripts("data/scripts/"); + } + + void notify(const event& e) + { + for (auto& script : get_scripts()) + { + script->notify(e); + } + } + + void run_frame() + { + for (auto& script : get_scripts()) + { + script->run_frame(); + } + } +} diff --git a/src/client/game/scripting/lua/engine.hpp b/src/client/game/scripting/lua/engine.hpp new file mode 100644 index 00000000..471316cd --- /dev/null +++ b/src/client/game/scripting/lua/engine.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "../event.hpp" + +namespace scripting::lua::engine +{ + void start(); + void stop(); + void notify(const event& e); + void run_frame(); +} diff --git a/src/client/game/scripting/lua/error.cpp b/src/client/game/scripting/lua/error.cpp new file mode 100644 index 00000000..935db0f0 --- /dev/null +++ b/src/client/game/scripting/lua/error.cpp @@ -0,0 +1,37 @@ +#include +#include "error.hpp" +#include "../execution.hpp" + +#include "component/console.hpp" + +namespace scripting::lua +{ + namespace + { + void notify_error() + { + try + { + call("iprintln", {"^1Script execution error!"}); + } + catch (...) + { + } + } + } + + void handle_error(const sol::protected_function_result& result) + { + if (!result.valid()) + { + console::error("************** Script execution error **************\n"); + + const sol::error err = result; + console::error("%s\n", err.what()); + + console::error("****************************************************\n"); + + notify_error(); + } + } +} diff --git a/src/client/game/scripting/lua/error.hpp b/src/client/game/scripting/lua/error.hpp new file mode 100644 index 00000000..dab56bee --- /dev/null +++ b/src/client/game/scripting/lua/error.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "context.hpp" + +namespace scripting::lua +{ + void handle_error(const sol::protected_function_result& result); +} diff --git a/src/client/game/scripting/lua/event_handler.cpp b/src/client/game/scripting/lua/event_handler.cpp new file mode 100644 index 00000000..7121ea63 --- /dev/null +++ b/src/client/game/scripting/lua/event_handler.cpp @@ -0,0 +1,174 @@ +#include "std_include.hpp" +#include "context.hpp" +#include "error.hpp" +#include "value_conversion.hpp" + +namespace scripting::lua +{ + event_handler::event_handler(sol::state& state) + : state_(state) + { + auto event_listener_handle_type = state.new_usertype("event_listener_handle"); + + event_listener_handle_type["clear"] = [this](const event_listener_handle& handle) + { + this->remove(handle); + }; + + event_listener_handle_type["endon"] = [this](const event_listener_handle& handle, const entity& entity, const std::string& event) + { + this->add_endon_condition(handle, entity, event); + }; + } + + void event_handler::dispatch(const event& event) + { + bool has_built_arguments = false; + event_arguments arguments{}; + + callbacks_.access([&](task_list& tasks) + { + this->merge_callbacks(); + this->handle_endon_conditions(event); + + for (auto i = tasks.begin(); i != tasks.end();) + { + if (i->event != event.name || i->entity != event.entity) + { + ++i; + continue; + } + + if (!i->is_deleted) + { + if(!has_built_arguments) + { + has_built_arguments = true; + arguments = this->build_arguments(event); + } + + handle_error(i->callback(sol::as_args(arguments))); + } + + if (i->is_volatile || i->is_deleted) + { + i = tasks.erase(i); + } + else + { + ++i; + } + } + }); + } + + event_listener_handle event_handler::add_event_listener(event_listener&& listener) + { + const uint64_t id = ++this->current_listener_id_; + listener.id = id; + listener.is_deleted = false; + + new_callbacks_.access([&listener](task_list& tasks) + { + tasks.emplace_back(std::move(listener)); + }); + + return {id}; + } + + void event_handler::add_endon_condition(const event_listener_handle& handle, const entity& entity, + const std::string& event) + { + auto merger = [&](task_list& tasks) + { + for(auto& task : tasks) + { + if(task.id == handle.id) + { + task.endon_conditions.emplace_back(entity, event); + } + } + }; + + callbacks_.access([&](task_list& tasks) + { + merger(tasks); + new_callbacks_.access(merger); + }); + } + + void event_handler::clear() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + new_tasks.clear(); + tasks.clear(); + }); + }); + } + + void event_handler::remove(const event_listener_handle& handle) + { + auto mask_as_deleted = [&](task_list& tasks) + { + for (auto& task : tasks) + { + if (task.id == handle.id) + { + task.is_deleted = true; + break; + } + } + }; + + callbacks_.access(mask_as_deleted); + new_callbacks_.access(mask_as_deleted); + } + + void event_handler::merge_callbacks() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), + std::move_iterator(new_tasks.end())); + new_tasks = {}; + }); + }); + } + + void event_handler::handle_endon_conditions(const event& event) + { + auto deleter = [&](task_list& tasks) + { + for(auto& task : tasks) + { + for(auto& condition : task.endon_conditions) + { + if(condition.first == event.entity && condition.second == event.name) + { + task.is_deleted = true; + break; + } + } + } + }; + + callbacks_.access(deleter); + } + + event_arguments event_handler::build_arguments(const event& event) const + { + event_arguments arguments; + + for (const auto& argument : event.arguments) + { + arguments.emplace_back(convert(this->state_, argument)); + } + + return arguments; + } +} diff --git a/src/client/game/scripting/lua/event_handler.hpp b/src/client/game/scripting/lua/event_handler.hpp new file mode 100644 index 00000000..fc95b704 --- /dev/null +++ b/src/client/game/scripting/lua/event_handler.hpp @@ -0,0 +1,58 @@ +#pragma once + +namespace scripting::lua +{ + using event_arguments = std::vector; + using event_callback = sol::protected_function; + + class event_listener_handle + { + public: + uint64_t id = 0; + }; + + class event_listener final : public event_listener_handle + { + public: + std::string event = {}; + entity entity{}; + event_callback callback = {}; + bool is_volatile = false; + bool is_deleted = false; + std::vector> endon_conditions{}; + }; + + class event_handler final + { + public: + event_handler(sol::state& state); + + event_handler(event_handler&&) noexcept = delete; + event_handler& operator=(event_handler&&) noexcept = delete; + + event_handler(const scheduler&) = delete; + event_handler& operator=(const event_handler&) = delete; + + void dispatch(const event& event); + + event_listener_handle add_event_listener(event_listener&& listener); + + void clear(); + + private: + sol::state& state_; + std::atomic_int64_t current_listener_id_ = 0; + + using task_list = std::vector; + utils::concurrency::container new_callbacks_; + utils::concurrency::container callbacks_; + + void remove(const event_listener_handle& handle); + void merge_callbacks(); + void handle_endon_conditions(const event& event); + + void add_endon_condition(const event_listener_handle& handle, const entity& entity, const std::string& event); + + event_arguments build_arguments(const event& event) const; + }; +} diff --git a/src/client/game/scripting/lua/scheduler.cpp b/src/client/game/scripting/lua/scheduler.cpp new file mode 100644 index 00000000..1afb7df2 --- /dev/null +++ b/src/client/game/scripting/lua/scheduler.cpp @@ -0,0 +1,171 @@ +#include "std_include.hpp" +#include "context.hpp" +#include "error.hpp" + +namespace scripting::lua +{ + scheduler::scheduler(sol::state& state) + { + auto task_handle_type = state.new_usertype("task_handle"); + + task_handle_type["clear"] = [this](const task_handle& handle) + { + this->remove(handle); + }; + + task_handle_type["endon"] = [this](const task_handle& handle, const entity& entity, const std::string& event) + { + this->add_endon_condition(handle, entity, event); + }; + } + + void scheduler::dispatch(const event& event) + { + auto deleter = [&](task_list& tasks) + { + for(auto& task : tasks) + { + for(auto& condition : task.endon_conditions) + { + if(condition.first == event.entity && condition.second == event.name) + { + task.is_deleted = true; + break; + } + } + } + }; + + callbacks_.access([&](task_list& tasks) + { + deleter(tasks); + new_callbacks_.access(deleter); + }); + } + + void scheduler::run_frame() + { + callbacks_.access([&](task_list& tasks) + { + this->merge_callbacks(); + + for (auto i = tasks.begin(); i != tasks.end();) + { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - i->last_call; + + if (diff < i->delay) + { + ++i; + continue; + } + + i->last_call = now; + + if (!i->is_deleted) + { + handle_error(i->callback()); + } + + if (i->is_volatile || i->is_deleted) + { + i = tasks.erase(i); + } + else + { + ++i; + } + } + }); + } + + void scheduler::clear() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + new_tasks.clear(); + tasks.clear(); + }); + }); + } + + task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds, + const bool is_volatile) + { + return this->add(callback, std::chrono::milliseconds(milliseconds), is_volatile); + } + + task_handle scheduler::add(const sol::protected_function& callback, const std::chrono::milliseconds delay, + const bool is_volatile) + { + const uint64_t id = ++this->current_task_id_; + + task task; + task.is_volatile = is_volatile; + task.callback = callback; + task.delay = delay; + task.last_call = std::chrono::steady_clock::now(); + task.id = id; + task.is_deleted = false; + + new_callbacks_.access([&task](task_list& tasks) + { + tasks.emplace_back(std::move(task)); + }); + + return {id}; + } + + void scheduler::add_endon_condition(const task_handle& handle, const entity& entity, const std::string& event) + { + auto merger = [&](task_list& tasks) + { + for(auto& task : tasks) + { + if(task.id == handle.id) + { + task.endon_conditions.emplace_back(entity, event); + } + } + }; + + callbacks_.access([&](task_list& tasks) + { + merger(tasks); + new_callbacks_.access(merger); + }); + } + + void scheduler::remove(const task_handle& handle) + { + auto mask_as_deleted = [&](task_list& tasks) + { + for (auto& task : tasks) + { + if (task.id == handle.id) + { + task.is_deleted = true; + break; + } + } + }; + + callbacks_.access(mask_as_deleted); + new_callbacks_.access(mask_as_deleted); + } + + void scheduler::merge_callbacks() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), + std::move_iterator(new_tasks.end())); + new_tasks = {}; + }); + }); + } +} diff --git a/src/client/game/scripting/lua/scheduler.hpp b/src/client/game/scripting/lua/scheduler.hpp new file mode 100644 index 00000000..17c90797 --- /dev/null +++ b/src/client/game/scripting/lua/scheduler.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +namespace scripting::lua +{ + class context; + + class task_handle + { + public: + uint64_t id = 0; + }; + + class task final : public task_handle + { + public: + std::chrono::steady_clock::time_point last_call{}; + sol::protected_function callback{}; + std::chrono::milliseconds delay{}; + bool is_volatile = false; + bool is_deleted = false; + std::vector> endon_conditions{}; + }; + + class scheduler final + { + public: + scheduler(sol::state& state); + + scheduler(scheduler&&) noexcept = delete; + scheduler& operator=(scheduler&&) noexcept = delete; + + scheduler(const scheduler&) = delete; + scheduler& operator=(const scheduler&) = delete; + + void dispatch(const event& event); + void run_frame(); + void clear(); + + task_handle add(const sol::protected_function& callback, long long milliseconds, bool is_volatile); + task_handle add(const sol::protected_function& callback, std::chrono::milliseconds delay, bool is_volatile); + + private: + using task_list = std::vector; + utils::concurrency::container new_callbacks_; + utils::concurrency::container callbacks_; + std::atomic_int64_t current_task_id_ = 0; + + void add_endon_condition(const task_handle& handle, const entity& entity, const std::string& event); + + void remove(const task_handle& handle); + void merge_callbacks(); + }; +} diff --git a/src/client/game/scripting/lua/value_conversion.cpp b/src/client/game/scripting/lua/value_conversion.cpp new file mode 100644 index 00000000..defbe14d --- /dev/null +++ b/src/client/game/scripting/lua/value_conversion.cpp @@ -0,0 +1,319 @@ +#include +#include "value_conversion.hpp" +#include "../functions.hpp" +#include "../execution.hpp" +#include ".../../component/logfile.hpp" + +namespace scripting::lua +{ + namespace + { + struct array_value + { + int index; + script_value value; + }; + + sol::lua_value entity_to_array(lua_State* state, unsigned int id) + { + auto table = sol::table::create(state); + auto metatable = sol::table::create(state); + + std::unordered_map values; + + const auto offset = 64000 * (id & 3); + + auto current = game::scr_VarGlob->objectVariableChildren[id].firstChild; + auto idx = 1; + + for (auto i = offset + current; current; i = offset + current) + { + const auto var = game::scr_VarGlob->childVariableValue[i]; + + if (var.type == game::SCRIPT_NONE) + { + current = var.nextSibling; + continue; + } + + const auto string_value = (game::scr_string_t)((unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8)); + const auto* str = game::SL_ConvertToString(string_value); + + std::string key = string_value < 0x40000 && str + ? str + : std::to_string(idx++); + + game::VariableValue variable{}; + variable.type = var.type; + variable.u = var.u.u; + + array_value value; + value.index = i; + value.value = variable; + + values[key] = value; + + current = var.nextSibling; + } + + table["getkeys"] = [values]() + { + std::vector _keys; + + for (const auto& entry : values) + { + _keys.push_back(entry.first); + } + + return _keys; + }; + + metatable[sol::meta_function::new_index] = [values](const sol::table t, const sol::this_state s, + const sol::lua_value& key_value, const sol::lua_value& value) + { + const auto key = key_value.is() + ? std::to_string(key_value.as()) + : key_value.as(); + + if (values.find(key) == values.end()) + { + return; + } + + const auto i = values.at(key).index; + const auto variable = &game::scr_VarGlob->childVariableValue[i]; + + const auto new_variable = convert({s, value}).get_raw(); + + game::AddRefToValue(new_variable.type, new_variable.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = (char)new_variable.type; + variable->u.u = new_variable.u; + }; + + metatable[sol::meta_function::index] = [values](const sol::table t, const sol::this_state s, + const sol::lua_value& key_value) + { + const auto key = key_value.is() + ? std::to_string(key_value.as()) + : key_value.as(); + + if (values.find(key) == values.end()) + { + return sol::lua_value{s, sol::lua_nil}; + } + + return convert(s, values.at(key).value); + }; + + metatable[sol::meta_function::length] = [values]() + { + return values.size(); + }; + + table[sol::metatable_key] = metatable; + + return {state, table}; + } + + game::VariableValue convert_function(sol::lua_value value) + { + const auto function = value.as(); + const auto index = reinterpret_cast(logfile::vm_execute_hooks.size()); + + logfile::vm_execute_hooks[index] = function; + + game::VariableValue func; + func.type = game::SCRIPT_FUNCTION; + func.u.codePosValue = index; + + return func; + } + + sol::lua_value convert_function(lua_State* state, const char* pos) + { + return sol::overload( + [pos](const entity& entity, const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, exec_ent_thread(entity, pos, arguments)); + }, + [pos](const sol::this_state s, sol::variadic_args va) + { + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + return convert(s, exec_ent_thread(*game::levelEntityId, pos, arguments)); + } + ); + } + } + + sol::lua_value entity_to_struct(lua_State* state, unsigned int parent_id) + { + auto table = sol::table::create(state); + auto metatable = sol::table::create(state); + + const auto offset = 64000 * (parent_id & 3); + + metatable[sol::meta_function::new_index] = [offset, parent_id](const sol::table t, const sol::this_state s, + const sol::lua_value& field, const sol::lua_value& value) + { + const auto id = field.is() + ? scripting::find_token_id(field.as()) + : field.as(); + + if (!id) + { + return; + } + + const auto variable_id = game::GetVariable(parent_id, id); + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset]; + const auto new_variable = convert({s, value}).get_raw(); + + game::AddRefToValue(new_variable.type, new_variable.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = (char)new_variable.type; + variable->u.u = new_variable.u; + }; + + metatable[sol::meta_function::index] = [offset, parent_id](const sol::table t, const sol::this_state s, + const sol::lua_value& field) + { + const auto id = field.is() + ? scripting::find_token_id(field.as()) + : field.as(); + + if (!id) + { + return sol::lua_value{s, sol::lua_nil}; + } + + const auto variable_id = game::FindVariable(parent_id, id); + if (!variable_id) + { + return sol::lua_value{s, sol::lua_nil}; + } + + const auto variable = game::scr_VarGlob->childVariableValue[variable_id + offset]; + + game::VariableValue result{}; + result.u = variable.u.u; + result.type = (game::scriptType_e)variable.type; + + return convert(s, result); + }; + + table[sol::metatable_key] = metatable; + + return {state, table}; + } + + script_value convert(const sol::lua_value& value) + { + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return {value.as()}; + } + + if (value.is()) + { + return convert_function(value); + } + + return {}; + } + + sol::lua_value convert(lua_State* state, const script_value& value) + { + if (value.is()) + { + return {state, value.as()}; + } + + if (value.is()) + { + return {state, value.as()}; + } + + if (value.is()) + { + return {state, value.as()}; + } + + if (value.is>()) + { + return entity_to_struct(state, value.get_raw().u.uintValue); + } + + if (value.is>()) + { + return entity_to_array(state, value.get_raw().u.uintValue); + } + + if (value.is>()) + { + return convert_function(state, value.get_raw().u.codePosValue); + } + + if (value.is()) + { + return {state, value.as()}; + } + + if (value.is()) + { + return {state, value.as()}; + } + + return {state, sol::lua_nil}; + } +} diff --git a/src/client/game/scripting/lua/value_conversion.hpp b/src/client/game/scripting/lua/value_conversion.hpp new file mode 100644 index 00000000..93256f80 --- /dev/null +++ b/src/client/game/scripting/lua/value_conversion.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "context.hpp" + +namespace scripting::lua +{ + sol::lua_value entity_to_struct(lua_State* state, unsigned int parent_id); + + script_value convert(const sol::lua_value& value); + sol::lua_value convert(lua_State* state, const script_value& value); +} diff --git a/src/client/game/scripting/safe_execution.cpp b/src/client/game/scripting/safe_execution.cpp new file mode 100644 index 00000000..92daa3b7 --- /dev/null +++ b/src/client/game/scripting/safe_execution.cpp @@ -0,0 +1,72 @@ +#include +#include "safe_execution.hpp" + +#pragma warning(push) +#pragma warning(disable: 4611) + +namespace scripting::safe_execution +{ + namespace + { + bool execute_with_seh(const script_function function, const game::scr_entref_t& entref) + { + __try + { + function(entref); + return true; + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } + } + } + + bool call(const script_function function, const game::scr_entref_t& entref) + { + *game::g_script_error_level += 1; + if (game::_setjmp(&game::g_script_error[*game::g_script_error_level])) + { + *game::g_script_error_level -= 1; + return false; + } + + const auto result = execute_with_seh(function, entref); + *game::g_script_error_level -= 1; + return result; + } + + bool set_entity_field(const game::scr_entref_t& entref, const int offset) + { + *game::g_script_error_level += 1; + if (game::_setjmp(&game::g_script_error[*game::g_script_error_level])) + { + *game::g_script_error_level -= 1; + return false; + } + + game::Scr_SetObjectField(entref.classnum, entref.entnum, offset); + + *game::g_script_error_level -= 1; + return true; + } + + bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value) + { + *game::g_script_error_level += 1; + if (game::_setjmp(&game::g_script_error[*game::g_script_error_level])) + { + value->type = game::SCRIPT_NONE; + value->u.intValue = 0; + *game::g_script_error_level -= 1; + return false; + } + + game::GetEntityFieldValue(value, entref.classnum, entref.entnum, offset); + + *game::g_script_error_level -= 1; + return true; + } +} + +#pragma warning(pop) diff --git a/src/client/game/scripting/safe_execution.hpp b/src/client/game/scripting/safe_execution.hpp new file mode 100644 index 00000000..6eea59d2 --- /dev/null +++ b/src/client/game/scripting/safe_execution.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "functions.hpp" + +namespace scripting::safe_execution +{ + bool call(script_function function, const game::scr_entref_t& entref); + + bool set_entity_field(const game::scr_entref_t& entref, int offset); + bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value); +} diff --git a/src/client/game/scripting/script_value.cpp b/src/client/game/scripting/script_value.cpp new file mode 100644 index 00000000..7d334f17 --- /dev/null +++ b/src/client/game/scripting/script_value.cpp @@ -0,0 +1,278 @@ +#include +#include "script_value.hpp" +#include "entity.hpp" + + +namespace scripting +{ + /*************************************************************** + * Constructors + **************************************************************/ + + script_value::script_value(const game::VariableValue& value) + : value_(value) + { + } + + script_value::script_value(const int value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_INTEGER; + variable.u.intValue = value; + + this->value_ = variable; + } + + script_value::script_value(const unsigned int value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_INTEGER; + variable.u.uintValue = value; + + this->value_ = variable; + } + + script_value::script_value(const bool value) + : script_value(static_cast(value)) + { + } + + script_value::script_value(const float value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_FLOAT; + variable.u.floatValue = value; + + this->value_ = variable; + } + + script_value::script_value(const double value) + : script_value(static_cast(value)) + { + } + + script_value::script_value(const char* value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_STRING; + variable.u.stringValue = game::SL_GetString(value, 0); + + const auto _ = gsl::finally([&variable]() + { + game::RemoveRefToValue(variable.type, variable.u); + }); + + this->value_ = variable; + } + + script_value::script_value(const std::string& value) + : script_value(value.data()) + { + } + + script_value::script_value(const entity& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_OBJECT; + variable.u.pointerValue = value.get_entity_id(); + + this->value_ = variable; + } + + script_value::script_value(const vector& value) + { + game::VariableValue variable{}; + variable.type = game::SCRIPT_VECTOR; + variable.u.vectorValue = game::Scr_AllocVector(value); + + const auto _ = gsl::finally([&variable]() + { + game::RemoveRefToValue(variable.type, variable.u); + }); + + this->value_ = variable; + } + + /*************************************************************** + * Integer + **************************************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_INTEGER; + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + int script_value::get() const + { + return this->get_raw().u.intValue; + } + + template <> + unsigned int script_value::get() const + { + return this->get_raw().u.uintValue; + } + + template <> + bool script_value::get() const + { + return this->get_raw().u.uintValue != 0; + } + + /*************************************************************** + * Float + **************************************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_FLOAT; + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + float script_value::get() const + { + return this->get_raw().u.floatValue; + } + + template <> + double script_value::get() const + { + return static_cast(this->get_raw().u.floatValue); + } + + /*************************************************************** + * String + **************************************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_STRING; + } + + template <> + bool script_value::is() const + { + return this->is(); + } + + template <> + const char* script_value::get() const + { + return game::SL_ConvertToString(static_cast(this->get_raw().u.stringValue)); + } + + template <> + std::string script_value::get() const + { + return this->get(); + } + + /*************************************************************** + * Array + **************************************************************/ + + template <> + bool script_value::is>() const + { + if (this->get_raw().type != game::SCRIPT_OBJECT) + { + return false; + } + + const auto id = this->get_raw().u.uintValue; + const auto type = game::scr_VarGlob->objectVariableValue[id].w.type; + + return type == game::SCRIPT_ARRAY; + } + + /*************************************************************** + * Struct + **************************************************************/ + + template <> + bool script_value::is>() const + { + if (this->get_raw().type != game::SCRIPT_OBJECT) + { + return false; + } + + const auto id = this->get_raw().u.uintValue; + const auto type = game::scr_VarGlob->objectVariableValue[id].w.type; + + return type == game::SCRIPT_STRUCT; + } + + /*************************************************************** + * Function + **************************************************************/ + + template <> + bool script_value::is>() const + { + return this->get_raw().type == game::SCRIPT_FUNCTION; + } + + /*************************************************************** + * Entity + **************************************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_OBJECT; + } + + template <> + entity script_value::get() const + { + return entity(this->get_raw().u.pointerValue); + } + + /*************************************************************** + * Vector + **************************************************************/ + + template <> + bool script_value::is() const + { + return this->get_raw().type == game::SCRIPT_VECTOR; + } + + template <> + vector script_value::get() const + { + return this->get_raw().u.vectorValue; + } + + /*************************************************************** + * + **************************************************************/ + + const game::VariableValue& script_value::get_raw() const + { + return this->value_.get(); + } +} diff --git a/src/client/game/scripting/script_value.hpp b/src/client/game/scripting/script_value.hpp new file mode 100644 index 00000000..df8a95b6 --- /dev/null +++ b/src/client/game/scripting/script_value.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "game/game.hpp" +#include "variable_value.hpp" +#include "vector.hpp" + +namespace scripting +{ + class entity; + + class script_value + { + public: + script_value() = default; + script_value(const game::VariableValue& value); + + script_value(int value); + script_value(unsigned int value); + script_value(bool value); + + script_value(float value); + script_value(double value); + + script_value(const char* value); + script_value(const std::string& value); + + script_value(const entity& value); + + script_value(const vector& value); + + template + bool is() const; + + template + T as() const + { + if (!this->is()) + { + throw std::runtime_error("Invalid type"); + } + + return get(); + } + + const game::VariableValue& get_raw() const; + + private: + template + T get() const; + + variable_value value_{}; + }; +} diff --git a/src/client/game/scripting/stack_isolation.cpp b/src/client/game/scripting/stack_isolation.cpp new file mode 100644 index 00000000..646e2584 --- /dev/null +++ b/src/client/game/scripting/stack_isolation.cpp @@ -0,0 +1,27 @@ +#include +#include "stack_isolation.hpp" + +namespace scripting +{ + stack_isolation::stack_isolation() + { + this->in_param_count_ = game::scr_VmPub->inparamcount; + this->out_param_count_ = game::scr_VmPub->outparamcount; + this->top_ = game::scr_VmPub->top; + this->max_stack_ = game::scr_VmPub->maxstack; + + game::scr_VmPub->top = this->stack_; + game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1]; + game::scr_VmPub->inparamcount = 0; + game::scr_VmPub->outparamcount = 0; + } + + stack_isolation::~stack_isolation() + { + game::Scr_ClearOutParams(); + game::scr_VmPub->inparamcount = this->in_param_count_; + game::scr_VmPub->outparamcount = this->out_param_count_; + game::scr_VmPub->top = this->top_; + game::scr_VmPub->maxstack = this->max_stack_; + } +} diff --git a/src/client/game/scripting/stack_isolation.hpp b/src/client/game/scripting/stack_isolation.hpp new file mode 100644 index 00000000..8dffd4bc --- /dev/null +++ b/src/client/game/scripting/stack_isolation.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + class stack_isolation final + { + public: + stack_isolation(); + ~stack_isolation(); + + stack_isolation(stack_isolation&&) = delete; + stack_isolation(const stack_isolation&) = delete; + stack_isolation& operator=(stack_isolation&&) = delete; + stack_isolation& operator=(const stack_isolation&) = delete; + + private: + game::VariableValue stack_[512]{}; + + game::VariableValue* max_stack_; + game::VariableValue* top_; + unsigned int in_param_count_; + unsigned int out_param_count_; + }; +} diff --git a/src/client/game/scripting/variable_value.cpp b/src/client/game/scripting/variable_value.cpp new file mode 100644 index 00000000..69a20023 --- /dev/null +++ b/src/client/game/scripting/variable_value.cpp @@ -0,0 +1,68 @@ +#include +#include "variable_value.hpp" + +namespace scripting +{ + variable_value::variable_value(const game::VariableValue& value) + { + this->assign(value); + } + + variable_value::variable_value(const variable_value& other) noexcept + { + this->operator=(other); + } + + variable_value::variable_value(variable_value&& other) noexcept + { + this->operator=(std::move(other)); + } + + variable_value& variable_value::operator=(const variable_value& other) noexcept + { + if (this != &other) + { + this->release(); + this->assign(other.value_); + } + + return *this; + } + + variable_value& variable_value::operator=(variable_value&& other) noexcept + { + if (this != &other) + { + this->release(); + this->value_ = other.value_; + other.value_.type = game::SCRIPT_NONE; + } + + return *this; + } + + variable_value::~variable_value() + { + this->release(); + } + + const game::VariableValue& variable_value::get() const + { + return this->value_; + } + + void variable_value::assign(const game::VariableValue& value) + { + this->value_ = value; + game::AddRefToValue(this->value_.type, this->value_.u); + } + + void variable_value::release() + { + if (this->value_.type != game::SCRIPT_NONE) + { + game::RemoveRefToValue(this->value_.type, this->value_.u); + this->value_.type = game::SCRIPT_NONE; + } + } +} diff --git a/src/client/game/scripting/variable_value.hpp b/src/client/game/scripting/variable_value.hpp new file mode 100644 index 00000000..7a962612 --- /dev/null +++ b/src/client/game/scripting/variable_value.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + class variable_value + { + public: + variable_value() = default; + variable_value(const game::VariableValue& value); + variable_value(const variable_value& other) noexcept; + variable_value(variable_value&& other) noexcept; + + variable_value& operator=(const variable_value& other) noexcept; + variable_value& operator=(variable_value&& other) noexcept; + + ~variable_value(); + + const game::VariableValue& get() const; + + private: + void assign(const game::VariableValue& value); + void release(); + + game::VariableValue value_{{0}, game::SCRIPT_NONE}; + }; +} diff --git a/src/client/game/scripting/vector.cpp b/src/client/game/scripting/vector.cpp new file mode 100644 index 00000000..e3b66dcd --- /dev/null +++ b/src/client/game/scripting/vector.cpp @@ -0,0 +1,85 @@ +#include +#include "vector.hpp" + +namespace scripting +{ + vector::vector(const float* value) + { + for (auto i = 0; i < 3; ++i) + { + this->value_[i] = value[i]; + } + } + + vector::vector(const game::vec3_t& value) + : vector(&value[0]) + { + } + + vector::vector(const float x, const float y, const float z) + { + this->value_[0] = x; + this->value_[1] = y; + this->value_[2] = z; + } + + vector::operator game::vec3_t&() + { + return this->value_; + } + + vector::operator const game::vec3_t&() const + { + return this->value_; + } + + game::vec_t& vector::operator[](const size_t i) + { + if (i >= 3) + { + throw std::runtime_error("Out of bounds."); + } + + return this->value_[i]; + } + + const game::vec_t& vector::operator[](const size_t i) const + { + if (i >= 3) + { + throw std::runtime_error("Out of bounds."); + } + + return this->value_[i]; + } + + float vector::get_x() const + { + return this->operator[](0); + } + + float vector::get_y() const + { + return this->operator[](1); + } + + float vector::get_z() const + { + return this->operator[](2); + } + + void vector::set_x(const float value) + { + this->operator[](0) = value; + } + + void vector::set_y(const float value) + { + this->operator[](1) = value; + } + + void vector::set_z(const float value) + { + this->operator[](2) = value; + } +} diff --git a/src/client/game/scripting/vector.hpp b/src/client/game/scripting/vector.hpp new file mode 100644 index 00000000..70b146d6 --- /dev/null +++ b/src/client/game/scripting/vector.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "game/game.hpp" + +namespace scripting +{ + class vector final + { + public: + vector() = default; + vector(const float* value); + vector(const game::vec3_t& value); + vector(float x, float y, float z); + + operator game::vec3_t&(); + operator const game::vec3_t&() const; + + game::vec_t& operator[](size_t i); + const game::vec_t& operator[](size_t i) const; + + float get_x() const; + float get_y() const; + float get_z() const; + + void set_x(float value); + void set_y(float value); + void set_z(float value); + + private: + game::vec3_t value_{0}; + }; +}