From 81e9ee451a70d2a91aae2caf3b6cbec3b34edf51 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 5 Apr 2022 01:30:24 +0200 Subject: [PATCH] Scripting changes + SP support --- premake5.lua | 1 + src/client/component/logfile.cpp | 2 +- src/client/component/scripting.cpp | 92 ++++++++++++++----- src/client/game/scripting/execution.cpp | 90 +++++++++--------- src/client/game/scripting/execution.hpp | 9 +- src/client/game/scripting/functions.cpp | 35 ++++++- src/client/game/scripting/functions.hpp | 2 +- src/client/game/scripting/lua/context.cpp | 41 ++++++++- src/client/game/scripting/lua/engine.cpp | 28 ++++-- src/client/game/scripting/lua/engine.hpp | 1 + .../game/scripting/lua/value_conversion.cpp | 55 ++++------- src/client/game/symbols.hpp | 7 +- src/client/game/ui_scripting/lua/engine.cpp | 11 +++ 13 files changed, 249 insertions(+), 125 deletions(-) diff --git a/premake5.lua b/premake5.lua index 16043516..9823d960 100644 --- a/premake5.lua +++ b/premake5.lua @@ -264,6 +264,7 @@ filter {} filter "configurations:Debug" optimize "Debug" + buildoptions {"/bigobj"} defines {"DEBUG", "_DEBUG"} filter {} diff --git a/src/client/component/logfile.cpp b/src/client/component/logfile.cpp index fc949d92..d1886ca0 100644 --- a/src/client/component/logfile.cpp +++ b/src/client/component/logfile.cpp @@ -212,7 +212,7 @@ namespace logfile return false; } - const auto hook = vm_execute_hooks[pos]; + const auto& hook = vm_execute_hooks[pos]; const auto state = hook.lua_state(); const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId); diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index e3116122..f8730c86 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -2,7 +2,6 @@ #include "loader/component_loader.hpp" #include "game/game.hpp" -#include #include "game/scripting/entity.hpp" #include "game/scripting/functions.hpp" @@ -13,6 +12,8 @@ #include "scheduler.hpp" #include "scripting.hpp" +#include + namespace scripting { std::unordered_map> fields_table; @@ -21,6 +22,7 @@ namespace scripting namespace { utils::hook::detour vm_notify_hook; + utils::hook::detour vm_execute_hook; utils::hook::detour scr_load_level_hook; utils::hook::detour g_shutdown_game_hook; @@ -29,7 +31,10 @@ namespace scripting utils::hook::detour scr_set_thread_position_hook; utils::hook::detour process_script_hook; + utils::hook::detour sl_get_canonical_string_hook; + std::string current_file; + unsigned int current_file_id{}; void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value, game::VariableValue* top) @@ -48,11 +53,6 @@ namespace scripting e.arguments.emplace_back(*value); } - if (e.name == "entitydeleted") - { - scripting::clear_entity_fields(e.entity); - } - lua::engine::notify(e); } } @@ -60,6 +60,16 @@ namespace scripting vm_notify_hook.invoke(notify_list_owner_id, string_value, top); } + unsigned int vm_execute_stub() + { + if (!lua::engine::is_running()) + { + lua::engine::start(); + } + + return vm_execute_hook.invoke(); + } + void scr_load_level_stub() { scr_load_level_hook.invoke(); @@ -71,20 +81,25 @@ namespace scripting void g_shutdown_game_stub(const int free_scripts) { + if (free_scripts) + { + script_function_table.clear(); + } + lua::engine::stop(); return g_shutdown_game_hook.invoke(free_scripts); } - void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t _name, unsigned int canonicalString, unsigned int offset) + void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset) { - const auto name = game::SL_ConvertToString(_name); + const auto name_str = game::SL_ConvertToString(name); - if (fields_table[classnum].find(name) == fields_table[classnum].end()) + if (fields_table[classnum].find(name_str) == fields_table[classnum].end()) { - fields_table[classnum][name] = offset; + fields_table[classnum][name_str] = offset; } - scr_add_class_field_hook.invoke(classnum, _name, canonicalString, offset); + scr_add_class_field_hook.invoke(classnum, name, canonical_string, offset); } void process_script_stub(const char* filename) @@ -92,21 +107,49 @@ namespace scripting const auto file_id = atoi(filename); if (file_id) { - current_file = scripting::find_token(file_id); + current_file_id = file_id; } else { + current_file_id = 0; current_file = filename; } process_script_hook.invoke(filename); } - void scr_set_thread_position_stub(unsigned int threadName, const char* codePos) + void add_function(const std::string& file, unsigned int id, const char* pos) { - const auto function_name = scripting::find_token(threadName); - script_function_table[current_file][function_name] = codePos; - scr_set_thread_position_hook.invoke(threadName, codePos); + const auto function_names = scripting::find_token(id); + for (const auto& name : function_names) + { + script_function_table[file][name] = pos; + } + } + + void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos) + { + if (current_file_id) + { + const auto names = scripting::find_token(current_file_id); + for (const auto& name : names) + { + add_function(name, thread_name, code_pos); + } + } + else + { + add_function(current_file, thread_name, code_pos); + } + + scr_set_thread_position_hook.invoke(thread_name, code_pos); + } + + unsigned int sl_get_canonical_string_stub(const char* str) + { + const auto result = sl_get_canonical_string_hook.invoke(str); + scripting::token_map[str] = result; + return result; } } @@ -115,11 +158,6 @@ namespace scripting public: void post_unpack() override { - if (game::environment::is_sp()) - { - return; - } - vm_notify_hook.create(SELECT_VALUE(0x140379A00, 0x1404479F0), vm_notify_stub); scr_add_class_field_hook.create(SELECT_VALUE(0x140370370, 0x14043E2C0), scr_add_class_field_stub); @@ -127,9 +165,19 @@ namespace scripting scr_set_thread_position_hook.create(SELECT_VALUE(0x14036A180, 0x140437D10), scr_set_thread_position_stub); process_script_hook.create(SELECT_VALUE(0x1403737E0, 0x1404417E0), process_script_stub); - scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub); + if (!game::environment::is_sp()) + { + scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub); + } + else + { + vm_execute_hook.create(SELECT_VALUE(0x140376590, 0x140444580), vm_execute_stub); + } + g_shutdown_game_hook.create(SELECT_VALUE(0x140277D40, 0x140345A60), g_shutdown_game_stub); + sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub); + scheduler::loop([]() { lua::engine::run_frame(); diff --git a/src/client/game/scripting/execution.cpp b/src/client/game/scripting/execution.cpp index 273b4f7a..1380ab1e 100644 --- a/src/client/game/scripting/execution.cpp +++ b/src/client/game/scripting/execution.cpp @@ -145,47 +145,6 @@ namespace scripting 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(); @@ -206,8 +165,7 @@ namespace scripting } else { - // Read custom fields - set_custom_field(entity, field, value); + set_object_variable(entity.get_entity_id(), field, value); } } @@ -234,8 +192,7 @@ namespace scripting return value; } - // Add custom fields - return get_custom_field(entity, field); + return get_object_variable(entity.get_entity_id(), field); } unsigned int make_array() @@ -248,4 +205,47 @@ namespace scripting return index; } + + void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value) + { + const auto offset = 0xFA00 * (parent_id & 3); + const auto variable_id = game::GetVariable(parent_id, id); + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset]; + const auto& raw_value = value.get_raw(); + + game::AddRefToValue(raw_value.type, raw_value.u); + game::RemoveRefToValue(variable->type, variable->u.u); + + variable->type = static_cast(raw_value.type); + variable->u.u = raw_value.u; + } + + void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value) + { + const auto id = scripting::find_token_id(name); + set_object_variable(parent_id, id, value); + } + + script_value get_object_variable(const unsigned int parent_id, const unsigned int id) + { + const auto offset = 0xFA00 * (parent_id & 3); + const auto variable_id = game::FindVariable(parent_id, id); + if (!variable_id) + { + return {}; + } + + const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset]; + game::VariableValue value{}; + value.type = static_cast(variable->type); + value.u = variable->u.u; + + return value; + } + + script_value get_object_variable(const unsigned int parent_id, const std::string& name) + { + const auto id = scripting::find_token_id(name); + return get_object_variable(parent_id, id); + } } diff --git a/src/client/game/scripting/execution.hpp b/src/client/game/scripting/execution.hpp index 94a2678d..9ec6f62f 100644 --- a/src/client/game/scripting/execution.hpp +++ b/src/client/game/scripting/execution.hpp @@ -27,13 +27,16 @@ namespace scripting 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); unsigned int make_array(); + + script_value get_object_variable(const unsigned int parent_id, const unsigned int id); + script_value get_object_variable(const unsigned int parent_id, const std::string& name); + + void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value); + void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value); } diff --git a/src/client/game/scripting/functions.cpp b/src/client/game/scripting/functions.cpp index 0f233547..ab2c0b9f 100644 --- a/src/client/game/scripting/functions.cpp +++ b/src/client/game/scripting/functions.cpp @@ -69,19 +69,40 @@ namespace scripting return reinterpret_cast(method_table)[index - 0x8000]; } + + unsigned int parse_token_id(const std::string& name) + { + if (name.starts_with("_ID")) + { + return static_cast(std::strtol(name.substr(3).data(), nullptr, 10)); + } + + if (name.starts_with("_id_")) + { + return static_cast(std::strtol(name.substr(4).data(), nullptr, 16)); + } + + return 0; + } } - std::string find_token(unsigned int id) + std::vector find_token(unsigned int id) { + std::vector results; + + results.push_back(utils::string::va("_id_%X", id)); + results.push_back(utils::string::va("_ID%i", id)); + for (const auto& token : token_map) { if (token.second == id) { - return token.first; + results.push_back(token.first); + break; } } - return utils::string::va("_ID%i", id); + return results; } unsigned int find_token_id(const std::string& name) @@ -93,7 +114,13 @@ namespace scripting return result->second; } - return 0; + const auto parsed_id = parse_token_id(name); + if (parsed_id) + { + return parsed_id; + } + + return game::SL_GetCanonicalString(name.data()); } script_function find_function(const std::string& name, const bool prefer_global) diff --git a/src/client/game/scripting/functions.hpp b/src/client/game/scripting/functions.hpp index 0422bcf7..95c51763 100644 --- a/src/client/game/scripting/functions.hpp +++ b/src/client/game/scripting/functions.hpp @@ -9,7 +9,7 @@ namespace scripting using script_function = void(*)(game::scr_entref_t); - std::string find_token(unsigned int id); + std::vector 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 index 7aaa9f89..cb24ff3e 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -17,7 +17,6 @@ namespace scripting::lua { namespace { - vector normalize_vector(const vector& vec) { const auto length = sqrt( @@ -155,12 +154,52 @@ namespace scripting::lua { return normalize_vector(a); }; + + vector_type["normalize"] = [](const vector& a) + { + return normalize_vector(a); + }; + + vector_type["toangles"] = [](const vector& a) + { + return call("vectortoangles", {a}).as(); + }; + + vector_type["toyaw"] = [](const vector& a) + { + return call("vectortoyaw", {a}).as(); + }; + + vector_type["tolerp"] = [](const vector& a) + { + return call("vectortolerp", {a}).as(); + }; + + vector_type["toup"] = [](const vector& a) + { + return call("anglestoup", {a}).as(); + }; + + vector_type["toright"] = [](const vector& a) + { + return call("anglestoright", {a}).as(); + }; + + vector_type["toforward"] = [](const vector& a) + { + return call("anglestoforward", {a}).as(); + }; } void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler) { state["level"] = entity{*game::levelEntityId}; + if (game::environment::is_sp()) + { + state["player"] = call("getentbynum", {0}).as(); + } + auto entity_type = state.new_usertype("entity"); for (const auto& func : method_map) diff --git a/src/client/game/scripting/lua/engine.cpp b/src/client/game/scripting/lua/engine.cpp index d481dc76..390a5995 100644 --- a/src/client/game/scripting/lua/engine.cpp +++ b/src/client/game/scripting/lua/engine.cpp @@ -11,6 +11,8 @@ namespace scripting::lua::engine { namespace { + bool running = false; + auto& get_scripts() { static std::vector> scripts{}; @@ -38,21 +40,30 @@ namespace scripting::lua::engine void stop() { + running = false; logfile::clear_callbacks(); get_scripts().clear(); } void start() { - // No SP until there is a concept - if (game::environment::is_sp()) - { - return; - } - stop(); + load_scripts("h1-mod/scripts/"); load_scripts("data/scripts/"); + + if (game::environment::is_sp()) + { + load_scripts("h1-mod/scripts/sp/"); + load_scripts("data/scripts/sp/"); + } + else + { + load_scripts("h1-mod/scripts/mp/"); + load_scripts("data/scripts/mp/"); + } + + running = true; } void notify(const event& e) @@ -70,4 +81,9 @@ namespace scripting::lua::engine script->run_frame(); } } + + bool is_running() + { + return running; + } } diff --git a/src/client/game/scripting/lua/engine.hpp b/src/client/game/scripting/lua/engine.hpp index 471316cd..2df0b0ef 100644 --- a/src/client/game/scripting/lua/engine.hpp +++ b/src/client/game/scripting/lua/engine.hpp @@ -8,4 +8,5 @@ namespace scripting::lua::engine void stop(); void notify(const event& e); void run_frame(); + bool is_running(); } diff --git a/src/client/game/scripting/lua/value_conversion.cpp b/src/client/game/scripting/lua/value_conversion.cpp index a838c787..a12abc8e 100644 --- a/src/client/game/scripting/lua/value_conversion.cpp +++ b/src/client/game/scripting/lua/value_conversion.cpp @@ -165,56 +165,33 @@ namespace scripting::lua 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, + metatable[sol::meta_function::new_index] = [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) + const auto new_variable = convert({s, value}); + if (field.is()) { - return; + scripting::set_object_variable(parent_id, field.as(), new_variable); + } + else if (field.is()) + { + scripting::set_object_variable(parent_id, field.as(), new_variable); } - - 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, + metatable[sol::meta_function::index] = [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) + if (field.is()) { - return sol::lua_value{s, sol::lua_nil}; + return convert(s, scripting::get_object_variable(parent_id, field.as())); + } + else if (field.is()) + { + return convert(s, scripting::get_object_variable(parent_id, field.as())); } - 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); + return sol::lua_value{s, sol::lua_nil}; }; table[sol::metatable_key] = metatable; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 1ade88ce..1f277cee 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -70,7 +70,7 @@ namespace game WEAK symbol FS_Startup{0x1403B85D0, 0x1404EDD30}; WEAK symbol FS_AddLocalizedGameDirectory{0x1403B6030, 0x1404EBE20}; - WEAK symbol GetVariable{0x14036FDD0, 0x1403F3730}; + WEAK symbol GetVariable{0x14036FDD0, 0x14043DD70}; WEAK symbol GetNewVariable{0x14036FA00, 0x14043D990}; WEAK symbol GetNewArrayVariable{0x14036F880, 0x14043D810}; WEAK symbol GScr_LoadConsts{0x1402D13E0, 0x140393810}; @@ -136,6 +136,7 @@ namespace game WEAK symbol Scr_ClearOutParams{0x140374460, 0x140442510}; WEAK symbol Scr_GetEntityIdRef{0x140372D50, 0x140440D80}; WEAK symbol Scr_GetEntityId{0x140372CA0, 0x140440CD0}; + WEAK symbol Scr_SetObjectField{0x1402B9F60, 0x140385330}; WEAK symbol ScrPlace_GetViewPlacement{0x1401981F0, 0x140288550}; @@ -155,8 +156,8 @@ namespace game WEAK symbol SL_FindString{0x14036D700, 0x14043B470}; WEAK symbol SL_GetString{0x14036D9A0, 0x14043B840}; - WEAK symbol SL_ConvertToString{0x14036D420, 0x14043B170}; - WEAK symbol Scr_SetObjectField{0x1402B9F60, 0x140385330}; + WEAK symbol SL_ConvertToString{0x14036D420, 0x14043B170}; + WEAK symbol SL_GetCanonicalString{0x14036A310, 0x140437EA0}; WEAK symbol SV_DirectConnect{0, 0x140480860}; WEAK symbol SV_Cmd_ArgvBuffer{0x1403446C0, 0x140404CA0}; diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index 8faf198c..cc3d3d33 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -54,6 +54,17 @@ namespace ui_scripting::lua::engine load_scripts("h1-mod/ui_scripts/"); load_scripts("data/ui_scripts/"); + + if (game::environment::is_sp()) + { + load_scripts("h1-mod/ui_scripts/sp/"); + load_scripts("data/ui_scripts/sp/"); + } + else + { + load_scripts("h1-mod/ui_scripts/mp/"); + load_scripts("data/ui_scripts/mp/"); + } } void stop()