From 4de550909296e22274f3b43389917bc8f5c9ccc5 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Fri, 10 Sep 2021 03:21:51 +0200 Subject: [PATCH 01/11] UI scripting test --- src/client/component/input.cpp | 20 + src/client/component/ui_scripting.cpp | 77 ++ src/client/component/ui_scripting.hpp | 6 + src/client/game/scripting/lua/context.cpp | 9 +- src/client/game/structs.hpp | 65 +- src/client/game/symbols.hpp | 3 + src/client/game/ui_scripting/element.cpp | 250 +++++ src/client/game/ui_scripting/element.hpp | 63 ++ src/client/game/ui_scripting/lua/context.cpp | 891 ++++++++++++++++++ src/client/game/ui_scripting/lua/context.hpp | 45 + src/client/game/ui_scripting/lua/engine.cpp | 67 ++ src/client/game/ui_scripting/lua/engine.hpp | 10 + src/client/game/ui_scripting/lua/error.cpp | 24 + src/client/game/ui_scripting/lua/error.hpp | 8 + src/client/game/ui_scripting/lua/event.hpp | 13 + .../game/ui_scripting/lua/event_handler.cpp | 107 +++ .../game/ui_scripting/lua/event_handler.hpp | 54 ++ .../game/ui_scripting/lua/scheduler.cpp | 122 +++ .../game/ui_scripting/lua/scheduler.hpp | 50 + src/client/game/ui_scripting/menu.cpp | 55 ++ src/client/game/ui_scripting/menu.hpp | 24 + 21 files changed, 1926 insertions(+), 37 deletions(-) create mode 100644 src/client/component/ui_scripting.cpp create mode 100644 src/client/component/ui_scripting.hpp create mode 100644 src/client/game/ui_scripting/element.cpp create mode 100644 src/client/game/ui_scripting/element.hpp create mode 100644 src/client/game/ui_scripting/lua/context.cpp create mode 100644 src/client/game/ui_scripting/lua/context.hpp create mode 100644 src/client/game/ui_scripting/lua/engine.cpp create mode 100644 src/client/game/ui_scripting/lua/engine.hpp create mode 100644 src/client/game/ui_scripting/lua/error.cpp create mode 100644 src/client/game/ui_scripting/lua/error.hpp create mode 100644 src/client/game/ui_scripting/lua/event.hpp create mode 100644 src/client/game/ui_scripting/lua/event_handler.cpp create mode 100644 src/client/game/ui_scripting/lua/event_handler.hpp create mode 100644 src/client/game/ui_scripting/lua/scheduler.cpp create mode 100644 src/client/game/ui_scripting/lua/scheduler.hpp create mode 100644 src/client/game/ui_scripting/menu.cpp create mode 100644 src/client/game/ui_scripting/menu.hpp diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index 11cd8d6c..175a5431 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -4,6 +4,7 @@ #include "game/game.hpp" #include "game_console.hpp" +#include "game/ui_scripting/lua/engine.hpp" #include @@ -11,11 +12,20 @@ namespace input { namespace { + struct point + { + short x; + short y; + }; + utils::hook::detour cl_char_event_hook; utils::hook::detour cl_key_event_hook; + utils::hook::detour cl_mouse_move_hook; void cl_char_event_stub(const int local_client_num, const int key) { + ui_scripting::lua::engine::ui_event("char", {key}); + if (!game_console::console_char_event(local_client_num, key)) { return; @@ -26,6 +36,8 @@ namespace input void cl_key_event_stub(const int local_client_num, const int key, const int down) { + ui_scripting::lua::engine::ui_event("key", {key, down}); + if (!game_console::console_key_event(local_client_num, key, down)) { return; @@ -33,6 +45,13 @@ namespace input cl_key_event_hook.invoke(local_client_num, key, down); } + + void cl_mouse_move_stub(const int local_client_num, int x, int y) + { + ui_scripting::lua::engine::ui_event("mousemove", {x, y}); + + cl_mouse_move_hook.invoke(local_client_num, x, y); + } } class component final : public component_interface @@ -42,6 +61,7 @@ namespace input { cl_char_event_hook.create(game::base_address + 0x3D27B0, cl_char_event_stub); cl_key_event_hook.create(game::base_address + 0x3D2AE0, cl_key_event_stub); + cl_mouse_move_hook.create(game::base_address + 0x3296F0, cl_mouse_move_stub); } }; } diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp new file mode 100644 index 00000000..0a5843c9 --- /dev/null +++ b/src/client/component/ui_scripting.cpp @@ -0,0 +1,77 @@ +#include +#include "loader/component_loader.hpp" + +#include "game/game.hpp" +#include "game/dvars.hpp" + +#include "chat.hpp" +#include "scheduler.hpp" +#include "command.hpp" +#include "ui_scripting.hpp" + +#include "game/ui_scripting/lua/engine.hpp" +#include "game/ui_scripting/lua/context.hpp" + +#include +#include + +namespace ui_scripting +{ + namespace + { + + } + + class component final : public component_interface + { + public: + + void post_unpack() override + { + scheduler::once([]() + { + ui_scripting::lua::engine::start(); + }, scheduler::pipeline::renderer); + + scheduler::loop([]() + { + ShowCursor(true); + ui_scripting::lua::engine::run_frame(); + }, scheduler::pipeline::renderer); + + command::add("reloadmenus", []() + { + scheduler::once([]() + { + ui_scripting::lua::engine::start(); + }, scheduler::pipeline::renderer); + }); + + command::add("openluamenu", [](const command::params& params) + { + printf("command openmenu\n"); + const std::string name = params.get(1); + scheduler::once([name]() + { + printf("command openmenu scheduler\n"); + ui_scripting::lua::open_menu(name); + }, scheduler::pipeline::renderer); + }); + + command::add("closeluamenu", [](const command::params& params) + { + printf("command closemenu\n"); + const std::string name = params.get(1); + scheduler::once([name]() + { + printf("command closemenu scheduler\n"); + ui_scripting::lua::close_menu(name); + }, scheduler::pipeline::renderer); + }); + + utils::hook::nop(game::base_address + 0x5F22B1, 5); + } + }; +} + +REGISTER_COMPONENT(ui_scripting::component) diff --git a/src/client/component/ui_scripting.hpp b/src/client/component/ui_scripting.hpp new file mode 100644 index 00000000..21cd1ad8 --- /dev/null +++ b/src/client/component/ui_scripting.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "game/ui_scripting/menu.hpp" + +namespace ui_scripting +{ +} diff --git a/src/client/game/scripting/lua/context.cpp b/src/client/game/scripting/lua/context.cpp index f7b0982d..09e95902 100644 --- a/src/client/game/scripting/lua/context.cpp +++ b/src/client/game/scripting/lua/context.cpp @@ -119,12 +119,9 @@ namespace scripting::lua vector_type[sol::meta_function::equal_to] = [](const vector& a, const vector& b) { - const auto normal_a = normalize_vector(a); - const auto normal_b = normalize_vector(b); - - return normal_a.get_x() == normal_b.get_x() && - normal_a.get_y() == normal_b.get_y() && - normal_a.get_z() == normal_b.get_z(); + return a.get_x() == b.get_x() && + a.get_y() == b.get_y() && + a.get_z() == b.get_z(); }; vector_type[sol::meta_function::length] = [](const vector& a) diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index a5b6661e..3c647ddb 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -409,11 +409,11 @@ namespace game enum XAssetType { ASSET_TYPE_PHYSPRESET, - ASSET_TYPE_PHYSCOLLMAP, + ASSET_TYPE_PHYS_COLLMAP, ASSET_TYPE_PHYSWATERPRESET, - ASSET_TYPE_PHYSWORLDMAP, + ASSET_TYPE_PHYS_WORLDMAP, ASSET_TYPE_PHYSCONSTRAINT, - ASSET_TYPE_XANIMPARTS, + ASSET_TYPE_XANIM, ASSET_TYPE_XMODELSURFS, ASSET_TYPE_XMODEL, ASSET_TYPE_MATERIAL, @@ -423,36 +423,35 @@ namespace game ASSET_TYPE_DOMAINSHADER, ASSET_TYPE_PIXELSHADER, ASSET_TYPE_VERTEXDECL, - ASSET_TYPE_TECHNIQUE_SET, + ASSET_TYPE_TECHSET, ASSET_TYPE_IMAGE, ASSET_TYPE_SOUND, - ASSET_TYPE_SOUND_SUBMIX, - ASSET_TYPE_SOUND_CURVE, - ASSET_TYPE_LPF_CURVE, - ASSET_TYPE_REVERB_CURVE, - ASSET_TYPE_SOUND_CONTEXT, + ASSET_TYPE_SOUNDSUBMIX, + ASSET_TYPE_SNDCURVE, + ASSET_TYPE_LPFCURVE, + ASSET_TYPE_REVERBSENDCURVE, + ASSET_TYPE_SNDCONTEXT, ASSET_TYPE_LOADED_SOUND, - ASSET_TYPE_CLIPMAP, - ASSET_TYPE_COMWORLD, - ASSET_TYPE_GLASSWORLD, - ASSET_TYPE_PATHDATA, + ASSET_TYPE_COL_MAP_SP, + ASSET_TYPE_COM_MAP, + ASSET_TYPE_GLASS_MAP, + ASSET_TYPE_AIPATHS, ASSET_TYPE_VEHICLE_TRACK, ASSET_TYPE_MAP_ENTS, - ASSET_TYPE_FXWORLD, - ASSET_TYPE_GFXWORLD, - ASSET_TYPE_LIGHT_DEF, + ASSET_TYPE_FX_MAP, + ASSET_TYPE_GFX_MAP, + ASSET_TYPE_LIGHTDEF, ASSET_TYPE_UI_MAP, - ASSET_TYPE_FONT, - ASSET_TYPE_MENULIST, + ASSET_TYPE_MENUFILE, ASSET_TYPE_MENU, ASSET_TYPE_ANIMCLASS, - ASSET_TYPE_LOCALIZE_ENTRY, + ASSET_TYPE_LOCALIZE, ASSET_TYPE_ATTACHMENT, ASSET_TYPE_WEAPON, - ASSET_TYPE_SNDDRIVER_GLOBALS, + ASSET_TYPE_SNDDRIVERGLOBALS, ASSET_TYPE_FX, - ASSET_TYPE_IMPACT_FX, - ASSET_TYPE_SURFACE_FX, + ASSET_TYPE_IMPACTFX, + ASSET_TYPE_SURFACEFX, ASSET_TYPE_AITYPE, ASSET_TYPE_MPTYPE, ASSET_TYPE_CHARACTER, @@ -460,22 +459,26 @@ namespace game ASSET_TYPE_RAWFILE, ASSET_TYPE_SCRIPTFILE, ASSET_TYPE_STRINGTABLE, - ASSET_TYPE_LEADERBOARD, - ASSET_TYPE_STRUCTURED_DATA_DEF, + ASSET_TYPE_LEADERBOARDDEF, + ASSET_TYPE_VIRTUALLEADERBOARDDEF, + ASSET_TYPE_STRUCTUREDDATADEF, + ASSET_TYPE_DDL, + ASSET_TYPE_PROTO, ASSET_TYPE_TRACER, ASSET_TYPE_VEHICLE, ASSET_TYPE_ADDON_MAP_ENTS, - ASSET_TYPE_NET_CONST_STRINGS, - ASSET_TYPE_REVERB_PRESET, - ASSET_TYPE_LUA_FILE, + ASSET_TYPE_NETCONSTSTRINGS, + ASSET_TYPE_REVERBPRESET, + ASSET_TYPE_LUAFILE, ASSET_TYPE_SCRIPTABLE, - ASSET_TYPE_EQUIPMENT_SND_TABLE, + ASSET_TYPE_EQUIPSNDTABLE, ASSET_TYPE_VECTORFIELD, - ASSET_TYPE_DOPPLER_PRESET, - ASSET_TYPE_PARTICLE_SIM_ANIMATION, + ASSET_TYPE_DOPPLERPRESET, + ASSET_TYPE_PARTICLESIMANIMATION, ASSET_TYPE_LASER, - ASSET_TYPE_SKELETON_SCRIPT, + ASSET_TYPE_SKELETONSCRIPT, ASSET_TYPE_CLUT, + ASSET_TYPE_TTF, ASSET_TYPE_COUNT, }; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 7a785a72..a740d9a8 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -43,6 +43,7 @@ namespace game unsigned int flags)> Dvar_RegisterVec4{0x6185F0}; WEAK symbol Dvar_ValueToString{0x61B8F0}; WEAK symbol Dvar_SetCommand{0x61A5C0}; + WEAK symbol Dvar_SetFromStringFromSource{0x61A910}; WEAK symbol generateHashValue{0x343D20}; @@ -78,6 +79,8 @@ namespace game WEAK symbol R_AddCmdDrawStretchPic{0x3C9710}; + WEAK symbol R_AddCmdDrawStretchPicRotateXY{0x3C99B0}; WEAK symbol R_AddCmdDrawText{0x76C660}; WEAK symbol +#include "element.hpp" + +#include + +#define fps_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 25) + +namespace ui_scripting +{ + namespace + { + uint64_t next_id; + + std::unordered_map font_map = + { + {"bank", "fonts/bank.ttf"}, + {"fira_mono_bold", "fonts/fira_mono_bold.ttf"}, + {"fira_mono_regular", "fonts/fira_mono_regular.ttf"}, + {"defaultbold", "fonts/defaultbold.otf"}, + {"default", "fonts/default.otf"}, + }; + + std::unordered_map alignment_map = + { + {"left", alignment::start}, + {"top", alignment::start}, + {"center", alignment::middle}, + {"right", alignment::end}, + {"bottom", alignment::end}, + }; + + float get_align_value(alignment align, float text_width, float w) + { + switch (align) + { + case (alignment::start): + return 0.f; + case (alignment::middle): + return (w / 2.f) - (text_width / 2.f); + case (alignment::end): + return w - text_width; + default: + return 0.f; + } + } + } + + element::element() + : id(next_id++) + { + } + + void element::set_horzalign(const std::string& value) + { + const auto lower = utils::string::to_lower(value); + if (alignment_map.find(lower) == alignment_map.end()) + { + this->horzalign = alignment::start; + return; + } + + const auto align = alignment_map[lower]; + this->horzalign = align; + } + + void element::set_vertalign(const std::string& value) + { + const auto lower = utils::string::to_lower(value); + if (alignment_map.find(lower) == alignment_map.end()) + { + this->vertalign = alignment::start; + return; + } + + const auto align = alignment_map[lower]; + this->vertalign = align; + } + + void element::set_text(const std::string& _text) + { + this->text = _text; + } + + void element::set_font(const std::string& _font, const int _fontsize) + { + this->fontsize = _fontsize; + const auto lowercase = utils::string::to_lower(_font); + + if (font_map.find(lowercase) == font_map.end()) + { + this->font = font_map["default"]; + } + else + { + this->font = font_map[lowercase]; + } + } + + void element::set_text_offset(float _x, float _y) + { + this->text_offset[0] = _x; + this->text_offset[1] = _y; + } + + void element::set_background_color(float r, float g, float b, float a) + { + this->background_color[0] = r; + this->background_color[1] = g; + this->background_color[2] = b; + this->background_color[3] = a; + } + + void element::set_color(float r, float g, float b, float a) + { + this->color[0] = r; + this->color[1] = g; + this->color[2] = b; + this->color[3] = a; + } + + void element::set_border_material(const std::string& _material) + { + this->border_material = _material; + } + + void element::set_border_color(float r, float g, float b, float a) + { + this->border_color[0] = r; + this->border_color[1] = g; + this->border_color[2] = b; + this->border_color[3] = a; + } + + void element::set_border_width(float top) + { + this->border_width[0] = top; + this->border_width[1] = top; + this->border_width[2] = top; + this->border_width[3] = top; + } + + void element::set_border_width(float top, float right) + { + this->border_width[0] = top; + this->border_width[1] = right; + this->border_width[2] = top; + this->border_width[3] = right; + } + + void element::set_border_width(float top, float right, float bottom) + { + this->border_width[0] = top; + this->border_width[1] = right; + this->border_width[2] = bottom; + this->border_width[3] = bottom; + } + + void element::set_border_width(float top, float right, float bottom, float left) + { + this->border_width[0] = top; + this->border_width[1] = right; + this->border_width[2] = bottom; + this->border_width[3] = left; + } + + void element::set_material(const std::string& _material) + { + this->material = _material; + } + + void element::set_rect(const float _x, const float _y, const float _w, const float _h) + { + this->x = _x; + this->y = _y; + this->w = _w; + this->h = _h; + } + + void element::render() const + { + const auto background_material = game::Material_RegisterHandle(this->material.data()); + game::R_AddCmdDrawStretchPic( + this->x + this->border_width[3], + this->y + this->border_width[0], + this->w, this->h, + 0.0f, 0.0f, 0.0f, 0.0f, + (float*)this->background_color, + background_material + ); + + const auto _border_material = game::Material_RegisterHandle(this->border_material.data()); + + game::R_AddCmdDrawStretchPic( + this->x, + this->y, + this->w + this->border_width[1] + this->border_width[3], + this->border_width[0], + 0.f, 0.f, 0.f, 0.f, + (float*)this->border_color, + _border_material + ); + + game::R_AddCmdDrawStretchPic( + this->x + this->border_width[3] + this->w, + this->y + this->border_width[0], + this->border_width[1], + this->h, + 0.f, 0.f, 0.f, 0.f, + (float*)this->border_color, + _border_material + ); + + game::R_AddCmdDrawStretchPic( + this->x, + this->y + this->h + this->border_width[0], + this->w + this->border_width[1] + this->border_width[3], + this->border_width[2], + 0.f, 0.f, 0.f, 0.f, + (float*)this->border_color, + _border_material + ); + + game::R_AddCmdDrawStretchPic( + this->x, + this->y + this->border_width[0], + this->border_width[3], + this->h, + 0.f, 0.f, 0.f, 0.f, + (float*)this->border_color, + _border_material + ); + + if (!this->text.empty()) + { + const auto _font = game::R_RegisterFont(this->font.data(), this->fontsize); + const auto text_width = game::R_TextWidth(this->text.data(), 0x7FFFFFFF, _font); + + auto _horzalign = get_align_value(this->horzalign, (float)text_width, this->w); + auto _vertalign = get_align_value(this->vertalign, (float)this->fontsize, this->h); + + game::R_AddCmdDrawText(this->text.data(), 0x7FFFFFFF, _font, + this->x + this->text_offset[0] + _horzalign + this->border_width[3], + this->y + this->text_offset[1] + _vertalign + this->fontsize + this->border_width[0], + 1.0f, 1.0f, 0.0f, + (float*)this->color, + 0 + ); + } + } +} diff --git a/src/client/game/ui_scripting/element.hpp b/src/client/game/ui_scripting/element.hpp new file mode 100644 index 00000000..2d9bd110 --- /dev/null +++ b/src/client/game/ui_scripting/element.hpp @@ -0,0 +1,63 @@ +#pragma once +#include "game/game.hpp" + +namespace ui_scripting +{ + enum alignment + { + start, + middle, + end, + }; + + class element final + { + public: + element(); + + void set_horzalign(const std::string& value); + void set_vertalign(const std::string& value); + + void set_text(const std::string& text); + void set_font(const std::string& _font, const int _fontsize); + void set_color(float r, float g, float b, float a); + void set_text_offset(float x, float y); + + void set_background_color(float r, float g, float b, float a); + void set_material(const std::string& material); + + void set_border_material(const std::string& material); + void set_border_color(float r, float g, float b, float a); + void set_border_width(float top); + void set_border_width(float top, float right); + void set_border_width(float top, float right, float bottom); + void set_border_width(float top, float right, float bottom, float left); + + void set_rect(const float _x, const float _y, const float _w, const float _h); + + uint64_t id; + + void render() const; + + float x = 0.f; + float y = 0.f; + float w = 0.f; + float h = 0.f; + + int fontsize = 20; + + float text_offset[2] = {0.f, 0.f}; + float color[4] = {1.f, 1.f, 1.f, 1.f}; + float background_color[4] = {0.f, 0.f, 0.f, 0.f}; + float border_color[4] = {0.f, 0.f, 0.f, 0.f}; + float border_width[4] = {0.f, 0.f, 0.f, 0.f}; + + alignment horzalign = alignment::start; + alignment vertalign = alignment::start; + + std::string font = "fonts/fira_mono_regular.ttf"; + std::string material = "white"; + std::string border_material = "white"; + std::string text{}; + }; +} diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp new file mode 100644 index 00000000..93d7cf5a --- /dev/null +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -0,0 +1,891 @@ +#include +#include "context.hpp" +#include "error.hpp" +#include "../../scripting/execution.hpp" + +#include "../../../component/ui_scripting.hpp" + +#include "component/game_console.hpp" +#include "component/scheduler.hpp" + +#include + +namespace ui_scripting::lua +{ + namespace + { + std::unordered_map menus; + std::vector elements; + element ui_element; + + scripting::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()}; + } + + return {}; + } + + bool point_in_rect(int px, int py, int x, int y, int w, int h) + { + return (px > x && px < x + w && py > y && py < y + h); + } + + void render_menus() + { + for (const auto& menu : menus) + { + if (menu.second.visible) + { + menu.second.render(); + } + } + } + + std::vector elements_in_point(int x, int y) + { + std::vector result; + + for (const auto& menu : menus) + { + if (!menu.second.visible) + { + continue; + } + + for (const auto& child : menu.second.children) + { + const auto in_rect = point_in_rect( + x, y, + (int)child->x, + (int)child->y, + (int)child->w + (int)child->border_width[1] + (int)child->border_width[3], + (int)child->h + (int)child->border_width[0] + (int)child->border_width[2] + ); + + if (in_rect) + { + result.push_back(child); + } + } + } + + return result; + } + + int mouse[2]; + + void handle_key_event(sol::state& state, event_handler& handler, const int key, const int down) + { + const auto _elements = elements_in_point(mouse[0], mouse[1]); + + switch (key) + { + case game::K_MOUSE2: + case game::K_MOUSE1: + { + const auto click_name = key == game::K_MOUSE1 + ? "click" + : "rightclick"; + + const auto key_name = key == game::K_MOUSE1 + ? "mouse" + : "rightmouse"; + + { + event main_event; + main_event.element = &ui_element; + main_event.name = utils::string::va("%s%s", key_name, down ? "down" : "up"); + main_event.arguments = + { + {state, mouse[0]}, + {state, mouse[1]}, + }; + + handler.dispatch(main_event); + + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = utils::string::va("%s%s", key_name, down ? "down" : "up"); + event.arguments = + { + {state, mouse[0]}, + {state, mouse[1]}, + }; + + handler.dispatch(event); + } + } + + if (!down) + { + event main_event; + main_event.element = &ui_element; + main_event.name = click_name; + main_event.arguments = + { + {state, mouse[0]}, + {state, mouse[1]}, + }; + + handler.dispatch(main_event); + + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = click_name; + event.arguments = + { + {state, mouse[0]}, + {state, mouse[1]}, + }; + + handler.dispatch(event); + } + } + + break; + } + case game::K_MWHEELUP: + case game::K_MWHEELDOWN: + { + const auto key_name = key == game::K_MWHEELUP + ? "scrollup" + : "scrolldown"; + + if (!down) + { + break; + } + + { + event main_event; + main_event.element = &ui_element; + main_event.name = key_name; + main_event.arguments = + { + {state, mouse[0]}, + {state, mouse[1]}, + }; + + handler.dispatch(main_event); + + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = key_name; + event.arguments = + { + {state, mouse[0]}, + {state, mouse[1]}, + }; + + handler.dispatch(event); + } + } + + break; + } + default: + { + event event; + event.element = &ui_element; + event.name = down + ? "keydown" + : "keyup"; + event.arguments = + { + {state, key}, + }; + + handler.dispatch(event); + + break; + } + } + } + + void handle_char_event(sol::state& state, event_handler& handler, const int key) + { + std::string key_str = {(char)key}; + event event; + event.element = &ui_element; + event.name = "keypress"; + event.arguments = + { + {state, key_str}, + }; + + handler.dispatch(event); + } + + std::vector previous_elements; + void handle_mousemove_event(sol::state& state, event_handler& handler, const int x, const int y) + { + mouse[0] = x; + mouse[1] = y; + + { + event event; + event.element = &ui_element; + event.name = "mousemove"; + event.arguments = + { + {state, x}, + {state, y}, + }; + + handler.dispatch(event); + } + + const auto _elements = elements_in_point(x, y); + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = "mouseover"; + + handler.dispatch(event); + } + + for (const auto& element : previous_elements) + { + auto found = false; + + for (const auto& _element : _elements) + { + if (element == _element) + { + found = true; + } + } + + if (!found) + { + event event; + event.element = element; + event.name = "mouseleave"; + + handler.dispatch(event); + } + } + + for (const auto& element : _elements) + { + auto found = false; + + for (const auto& _element : previous_elements) + { + if (element == _element) + { + found = true; + } + } + + if (!found) + { + event event; + event.element = element; + event.name = "mouseenter"; + + handler.dispatch(event); + } + } + + previous_elements = _elements; + } + + bool valid_dvar_name(const std::string& name) + { + for (const auto c : name) + { + if (!isalnum(c)) + { + return false; + } + } + + return true; + } + + void clear_menus() + { + menus.clear(); + for (const auto element : elements) + { + delete element; + } + + elements.clear(); + } + + void setup_types(sol::state& state, event_handler& handler, scheduler& scheduler) + { + clear_menus(); + + auto vector_type = state.new_usertype("scripting::vector", sol::constructors()); + vector_type["x"] = sol::property(&scripting::vector::get_x, &scripting::vector::set_x); + vector_type["y"] = sol::property(&scripting::vector::get_y, &scripting::vector::set_y); + vector_type["z"] = sol::property(&scripting::vector::get_z, &scripting::vector::set_z); + + vector_type["r"] = sol::property(&scripting::vector::get_x, &scripting::vector::set_x); + vector_type["g"] = sol::property(&scripting::vector::get_y, &scripting::vector::set_y); + vector_type["b"] = sol::property(&scripting::vector::get_z, &scripting::vector::set_z); + + vector_type[sol::meta_function::addition] = sol::overload( + [](const scripting::vector& a, const scripting::vector& b) + { + return scripting::vector( + a.get_x() + b.get_x(), + a.get_y() + b.get_y(), + a.get_z() + b.get_z() + ); + }, + [](const scripting::vector& a, const int value) + { + return scripting::vector( + a.get_x() + value, + a.get_y() + value, + a.get_z() + value + ); + } + ); + + vector_type[sol::meta_function::subtraction] = sol::overload( + [](const scripting::vector& a, const scripting::vector& b) + { + return scripting::vector( + a.get_x() - b.get_x(), + a.get_y() - b.get_y(), + a.get_z() - b.get_z() + ); + }, + [](const scripting::vector& a, const int value) + { + return scripting::vector( + a.get_x() - value, + a.get_y() - value, + a.get_z() - value + ); + } + ); + + vector_type[sol::meta_function::multiplication] = sol::overload( + [](const scripting::vector& a, const scripting::vector& b) + { + return scripting::vector( + a.get_x() * b.get_x(), + a.get_y() * b.get_y(), + a.get_z() * b.get_z() + ); + }, + [](const scripting::vector& a, const int value) + { + return scripting::vector( + a.get_x() * value, + a.get_y() * value, + a.get_z() * value + ); + } + ); + + vector_type[sol::meta_function::division] = sol::overload( + [](const scripting::vector& a, const scripting::vector& b) + { + return scripting::vector( + a.get_x() / b.get_x(), + a.get_y() / b.get_y(), + a.get_z() / b.get_z() + ); + }, + [](const scripting::vector& a, const int value) + { + return scripting::vector( + a.get_x() / value, + a.get_y() / value, + a.get_z() / value + ); + } + ); + + vector_type[sol::meta_function::equal_to] = [](const scripting::vector& a, const scripting::vector& b) + { + return a.get_x() == b.get_x() && + a.get_y() == b.get_y() && + a.get_z() == b.get_z(); + }; + + vector_type[sol::meta_function::length] = [](const scripting::vector& a) + { + return sqrt((a.get_x() * a.get_x()) + (a.get_y() * a.get_y()) + (a.get_z() * a.get_z())); + }; + + vector_type[sol::meta_function::to_string] = [](const scripting::vector& a) + { + return utils::string::va("{x: %f, y: %f, z: %f}", a.get_x(), a.get_y(), a.get_z()); + }; + + auto element_type = state.new_usertype("element", "new", []() + { + const auto el = new element(); + elements.push_back(el); + return el; + }); + + element_type["setvertalign"] = &element::set_vertalign; + element_type["sethorzalign"] = &element::set_horzalign; + element_type["setrect"] =&element::set_rect; + element_type["setfont"] = &element::set_font; + element_type["settext"] = &element::set_text; + element_type["setmaterial"] = &element::set_material; + element_type["setcolor"] = &element::set_color; + element_type["setbackcolor"] = &element::set_background_color; + element_type["setbordercolor"] = &element::set_border_color; + element_type["setborderwidth"] = sol::overload( + static_cast(&element::set_border_width), + static_cast(&element::set_border_width), + static_cast(&element::set_border_width), + static_cast(&element::set_border_width) + ); + element_type["settextoffset"] = &element::set_text_offset; + + element_type["onnotify"] = [&handler](element& element, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &element; + listener.event = event; + listener.is_volatile = false; + + return handler.add_event_listener(std::move(listener)); + }; + + element_type["getrect"] = [](const sol::this_state s, element& element) + { + auto rect = sol::table::create(s.lua_state()); + rect["x"] = element.x; + rect["y"] = element.y; + rect["w"] = element.w + element.border_width[1] + element.border_width[3]; + rect["h"] = element.h + element.border_width[0] + element.border_width[2]; + + return rect; + }; + + element_type["onnotifyonce"] = [&handler](element& element, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &element; + listener.event = event; + listener.is_volatile = true; + + return handler.add_event_listener(std::move(listener)); + }; + + auto menu_type = state.new_usertype("menu"); + + menu_type["addchild"] = [](const sol::this_state s, menu& menu, element& element) + { + menu.add_child(&element); + }; + + menu_type["cursor"] = sol::property( + [](menu& menu) + { + return menu.cursor; + }, + [](menu& menu, bool cursor) + { + menu.cursor = cursor; + } + ); + + struct game + { + }; + auto game_type = state.new_usertype("game_"); + state["game"] = game(); + + game_type["newmenu"] = [](const sol::lua_value&, const std::string& name) + { + menus[name] = {}; + return &menus[name]; + }; + + game_type["getmouseposition"] = [](const sol::this_state s, const game&) + { + auto pos = sol::table::create(s.lua_state()); + pos["x"] = mouse[0]; + pos["y"] = mouse[1]; + + return pos; + }; + + game_type["openmenu"] = [](const game&, const std::string& name) + { + if (menus.find(name) == menus.end()) + { + return; + } + + menus[name].open(); + }; + + game_type["closemenu"] = [](const game&, const std::string& name) + { + if (menus.find(name) == menus.end()) + { + return; + } + + menus[name].close(); + }; + + game_type["onframe"] = [&scheduler](const game&, const sol::protected_function& callback) + { + return scheduler.add(callback, 0, false); + }; + + game_type["onnotify"] = [&handler](const game&, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &ui_element; + listener.event = event; + listener.is_volatile = false; + + return handler.add_event_listener(std::move(listener)); + }; + + game_type["onnotifyonce"] = [&handler](const game&, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &ui_element; + listener.event = event; + listener.is_volatile = true; + + return handler.add_event_listener(std::move(listener)); + }; + + game_type["isingame"] = []() + { + return ::game::CL_IsCgameInitialized() && ::game::g_entities[0].client; + }; + + game_type["getdvar"] = [](const game&, const sol::this_state s, const std::string& name) + { + const auto dvar = ::game::Dvar_FindVar(name.data()); + if (!dvar) + { + return sol::lua_value{s, sol::lua_nil}; + } + + const std::string value = ::game::Dvar_ValueToString(dvar, nullptr, &dvar->current); + return sol::lua_value{s, value}; + }; + + game_type["getdvarint"] = [](const game&, const sol::this_state s, const std::string& name) + { + const auto dvar = ::game::Dvar_FindVar(name.data()); + if (!dvar) + { + return sol::lua_value{s, sol::lua_nil}; + } + + const auto value = atoi(::game::Dvar_ValueToString(dvar, nullptr, &dvar->current)); + return sol::lua_value{s, value}; + }; + + game_type["getdvarfloat"] = [](const game&, const sol::this_state s, const std::string& name) + { + const auto dvar = ::game::Dvar_FindVar(name.data()); + if (!dvar) + { + return sol::lua_value{s, sol::lua_nil}; + } + + const auto value = atof(::game::Dvar_ValueToString(dvar, nullptr, &dvar->current)); + return sol::lua_value{s, value}; + }; + + game_type["setdvar"] = [](const game&, const std::string& name, const sol::lua_value& value) + { + if (!valid_dvar_name(name)) + { + throw std::runtime_error("Invalid DVAR name, must be alphanumeric"); + } + + const auto hash = ::game::generateHashValue(name.data()); + std::string string_value; + + if (value.is()) + { + string_value = utils::string::va("%i", value.as()); + } + else if (value.is()) + { + string_value = utils::string::va("%i", value.as()); + } + else if (value.is()) + { + string_value = utils::string::va("%f", value.as()); + } + else if (value.is()) + { + const auto v = value.as(); + string_value = utils::string::va("%f %f %f", + v.get_x(), + v.get_y(), + v.get_z() + ); + } + + if (value.is()) + { + string_value = value.as(); + } + + ::game::Dvar_SetCommand(hash, "", string_value.data()); + }; + + game_type["drawmaterial"] = [](const game&, float x, float y, float width, float height, float s0, float t0, float s1, float t1, + const sol::lua_value& color_value, const std::string& material) + { + const auto color = color_value.as>(); + float _color[4] = + { + color[0], + color[1], + color[2], + color[3], + }; + + const auto _material = ::game::Material_RegisterHandle(material.data()); + ::game::R_AddCmdDrawStretchPic(x, y, width, height, s0, t0, s1, t1, _color, _material); + }; + + struct player + { + }; + auto player_type = state.new_usertype("player_"); + state["player"] = player(); + + player_type["notify"] = [](const player&, const sol::this_state s, const std::string& name, sol::variadic_args va) + { + if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client) + { + throw std::runtime_error("Not in game"); + } + + std::vector arguments{}; + + for (auto arg : va) + { + arguments.push_back(convert({s, arg})); + } + + ::scheduler::once([s, va, name, arguments]() + { + const auto player = scripting::call("getentbynum", {0}).as(); + scripting::notify(player, name, arguments); + }, ::scheduler::pipeline::server); + }; + + player_type["getorigin"] = [](const player&) + { + if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client) + { + throw std::runtime_error("Not in game"); + } + + return scripting::vector( + ::game::g_entities[0].origin[0], + ::game::g_entities[0].origin[1], + ::game::g_entities[0].origin[2] + ); + }; + + player_type["setorigin"] = [](const player&, const scripting::vector& velocity) + { + if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client) + { + throw std::runtime_error("Not in game"); + } + + ::game::g_entities[0].origin[0] = velocity.get_x(); + ::game::g_entities[0].origin[1] = velocity.get_y(); + ::game::g_entities[0].origin[2] = velocity.get_z(); + }; + + player_type["getvelocity"] = [](const player&) + { + if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client) + { + throw std::runtime_error("Not in game"); + } + + return scripting::vector( + ::game::g_entities[0].client->velocity[0], + ::game::g_entities[0].client->velocity[1], + ::game::g_entities[0].client->velocity[2] + ); + }; + + player_type["setvelocity"] = [](const player&, const scripting::vector& velocity) + { + if (!::game::CL_IsCgameInitialized() || !::game::g_entities[0].client) + { + throw std::runtime_error("Not in game"); + } + + ::game::g_entities[0].client->velocity[0] = velocity.get_x(); + ::game::g_entities[0].client->velocity[1] = velocity.get_y(); + ::game::g_entities[0].client->velocity[2] = velocity.get_z(); + }; + } + } + + void open_menu(const std::string& name) + { + printf("open_menu\n"); + + if (menus.find(name) == menus.end()) + { + return; + } + + menus[name].open(); + } + + void close_menu(const std::string& name) + { + printf("close_menu\n"); + + if (menus.find(name) == menus.end()) + { + return; + } + + menus[name].close(); + } + + 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_types(this->state_, this->event_handler_, this->scheduler_); + + printf("Loading ui script '%s'\n", this->folder_.data()); + this->load_script("__init__"); + } + + context::~context() + { + this->state_.collect_garbage(); + this->scheduler_.clear(); + this->state_ = {}; + } + + void context::run_frame() + { + render_menus(); + this->scheduler_.run_frame(); + this->state_.collect_garbage(); + } + + void context::ui_event(const std::string& type, const std::vector& arguments) + { + if (type == "key") + { + handle_key_event(this->state_, this->event_handler_, arguments[0], arguments[1]); + } + + if (type == "char") + { + handle_char_event(this->state_, this->event_handler_, arguments[0]); + } + + if (type == "mousemove") + { + handle_mousemove_event(this->state_, this->event_handler_, arguments[0], arguments[1]); + } + } + + 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/ui_scripting/lua/context.hpp b/src/client/game/ui_scripting/lua/context.hpp new file mode 100644 index 00000000..b2a88f59 --- /dev/null +++ b/src/client/game/ui_scripting/lua/context.hpp @@ -0,0 +1,45 @@ +#pragma once + +#pragma warning(push) +#pragma warning(disable: 4702) + +#define SOL_ALL_SAFETIES_ON 1 +#define SOL_PRINT_ERRORS 0 +#include + +#include "../menu.hpp" +#include "event.hpp" +#include "scheduler.hpp" +#include "event_handler.hpp" + +namespace ui_scripting::lua +{ + void open_menu(const std::string& name); + void close_menu(const std::string& name); + + 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 ui_event(const std::string&, const std::vector&); + + 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/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp new file mode 100644 index 00000000..0e0ff93a --- /dev/null +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -0,0 +1,67 @@ +#include +#include "engine.hpp" +#include "context.hpp" + +#include +#include + +namespace ui_scripting::lua::engine +{ + namespace + { + auto& get_scripts() + { + static std::vector> scripts{}; + return scripts; + } + + void load_scripts() + { + const auto script_dir = "ui_scripts/"s; + + if (!utils::io::directory_exists(script_dir)) + { + return; + } + + printf("here\n"); + + 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 start() + { + get_scripts().clear(); + load_scripts(); + } + + void stop() + { + get_scripts().clear(); + } + + void ui_event(const std::string& type, const std::vector& arguments) + { + for (auto& script : get_scripts()) + { + script->ui_event(type, arguments); + } + } + + void run_frame() + { + for (auto& script : get_scripts()) + { + script->run_frame(); + } + } +} diff --git a/src/client/game/ui_scripting/lua/engine.hpp b/src/client/game/ui_scripting/lua/engine.hpp new file mode 100644 index 00000000..0d33fccf --- /dev/null +++ b/src/client/game/ui_scripting/lua/engine.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace ui_scripting::lua::engine +{ + void start(); + void stop(); + + void ui_event(const std::string&, const std::vector&); + void run_frame(); +} diff --git a/src/client/game/ui_scripting/lua/error.cpp b/src/client/game/ui_scripting/lua/error.cpp new file mode 100644 index 00000000..6e021a3b --- /dev/null +++ b/src/client/game/ui_scripting/lua/error.cpp @@ -0,0 +1,24 @@ +#include +#include "error.hpp" + +namespace ui_scripting::lua +{ + void handle_error(const sol::protected_function_result& result) + { + if (!result.valid()) + { + try + { + printf("************** UI Script execution error **************\n"); + + const sol::error err = result; + printf("%s\n", err.what()); + + printf("****************************************************\n"); + } + catch (...) + { + } + } + } +} diff --git a/src/client/game/ui_scripting/lua/error.hpp b/src/client/game/ui_scripting/lua/error.hpp new file mode 100644 index 00000000..28a5c453 --- /dev/null +++ b/src/client/game/ui_scripting/lua/error.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "context.hpp" + +namespace ui_scripting::lua +{ + void handle_error(const sol::protected_function_result& result); +} diff --git a/src/client/game/ui_scripting/lua/event.hpp b/src/client/game/ui_scripting/lua/event.hpp new file mode 100644 index 00000000..a90fa6d8 --- /dev/null +++ b/src/client/game/ui_scripting/lua/event.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "context.hpp" + +namespace ui_scripting::lua +{ + struct event + { + std::string name; + element* element{}; + std::vector arguments; + }; +} diff --git a/src/client/game/ui_scripting/lua/event_handler.cpp b/src/client/game/ui_scripting/lua/event_handler.cpp new file mode 100644 index 00000000..86947d65 --- /dev/null +++ b/src/client/game/ui_scripting/lua/event_handler.cpp @@ -0,0 +1,107 @@ +#include "std_include.hpp" +#include "context.hpp" +#include "error.hpp" + +#include "event_handler.hpp" + +namespace ui_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); + }; + } + + void event_handler::dispatch(const event& event) + { + callbacks_.access([&](task_list& tasks) + { + this->merge_callbacks(); + + for (auto i = tasks.begin(); i != tasks.end();) + { + if (i->event != event.name || i->element->id != event.element->id) + { + ++i; + continue; + } + + if (!i->is_deleted) + { + handle_error(i->callback(sol::as_args(event.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::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 = {}; + }); + }); + } +} diff --git a/src/client/game/ui_scripting/lua/event_handler.hpp b/src/client/game/ui_scripting/lua/event_handler.hpp new file mode 100644 index 00000000..4041b432 --- /dev/null +++ b/src/client/game/ui_scripting/lua/event_handler.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include "../element.hpp" + +namespace ui_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 = {}; + element* element{}; + event_callback callback = {}; + bool is_volatile = false; + bool is_deleted = false; + }; + + 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(); + }; +} diff --git a/src/client/game/ui_scripting/lua/scheduler.cpp b/src/client/game/ui_scripting/lua/scheduler.cpp new file mode 100644 index 00000000..b40d191a --- /dev/null +++ b/src/client/game/ui_scripting/lua/scheduler.cpp @@ -0,0 +1,122 @@ +#include "std_include.hpp" +#include "context.hpp" +#include "error.hpp" + +namespace ui_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); + }; + } + + 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::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/ui_scripting/lua/scheduler.hpp b/src/client/game/ui_scripting/lua/scheduler.hpp new file mode 100644 index 00000000..1935e25e --- /dev/null +++ b/src/client/game/ui_scripting/lua/scheduler.hpp @@ -0,0 +1,50 @@ +#pragma once +#include + +namespace ui_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; + }; + + 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 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 remove(const task_handle& handle); + void merge_callbacks(); + }; +} diff --git a/src/client/game/ui_scripting/menu.cpp b/src/client/game/ui_scripting/menu.cpp new file mode 100644 index 00000000..c535b49f --- /dev/null +++ b/src/client/game/ui_scripting/menu.cpp @@ -0,0 +1,55 @@ +#include +#include "menu.hpp" +#include "lua/engine.hpp" +#include "component/ui_scripting.hpp" + +namespace ui_scripting +{ + menu::menu() + { + } + + void menu::add_child(element* el) + { + this->children.push_back(el); + } + + void menu::open() + { + if (this->visible) + { + return; + } + + this->cursor_was_enabled = *game::keyCatchers & 0x40; + if (!this->cursor_was_enabled && this->cursor) + { + *game::keyCatchers |= 0x40; + } + + this->visible = true; + } + + void menu::close() + { + if (!this->visible) + { + return; + } + + if (!this->cursor_was_enabled && this->cursor) + { + *game::keyCatchers &= ~0x40; + } + + this->visible = false; + } + + void menu::render() const + { + for (auto& element : this->children) + { + element->render(); + } + } +} diff --git a/src/client/game/ui_scripting/menu.hpp b/src/client/game/ui_scripting/menu.hpp new file mode 100644 index 00000000..b8936bf7 --- /dev/null +++ b/src/client/game/ui_scripting/menu.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "game/game.hpp" +#include "element.hpp" + +namespace ui_scripting +{ + class menu final + { + public: + menu(); + + bool visible = false; + bool cursor = false; + bool cursor_was_enabled = false; + + void open(); + void close(); + + void add_child(element* el); + void render() const; + + std::vector children{}; + }; +} From 8c55244c520b0a828e0cd669d42988114c794841 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 12 Sep 2021 02:26:53 +0200 Subject: [PATCH 02/11] UI scripting progress --- src/client/component/images.cpp | 2 +- src/client/component/ui_scripting.cpp | 12 -- src/client/game/structs.hpp | 16 ++ src/client/game/symbols.hpp | 2 + src/client/game/ui_scripting/element.cpp | 147 ++++++++++++++----- src/client/game/ui_scripting/element.hpp | 3 + src/client/game/ui_scripting/lua/context.cpp | 48 +++++- src/client/game/ui_scripting/lua/engine.cpp | 2 - 8 files changed, 174 insertions(+), 58 deletions(-) diff --git a/src/client/component/images.cpp b/src/client/component/images.cpp index 531a3e44..e82c532c 100644 --- a/src/client/component/images.cpp +++ b/src/client/component/images.cpp @@ -34,7 +34,7 @@ namespace images return {}; } - return { std::move(data) }; + return {std::move(data)}; } std::optional load_raw_image_from_file(game::GfxImage* image) diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 0a5843c9..613dedb0 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -13,15 +13,9 @@ #include "game/ui_scripting/lua/context.hpp" #include -#include namespace ui_scripting { - namespace - { - - } - class component final : public component_interface { public: @@ -49,27 +43,21 @@ namespace ui_scripting command::add("openluamenu", [](const command::params& params) { - printf("command openmenu\n"); const std::string name = params.get(1); scheduler::once([name]() { - printf("command openmenu scheduler\n"); ui_scripting::lua::open_menu(name); }, scheduler::pipeline::renderer); }); command::add("closeluamenu", [](const command::params& params) { - printf("command closemenu\n"); const std::string name = params.get(1); scheduler::once([name]() { - printf("command closemenu scheduler\n"); ui_scripting::lua::close_menu(name); }, scheduler::pipeline::renderer); }); - - utils::hook::nop(game::base_address + 0x5F22B1, 5); } }; } diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 3c647ddb..ab366054 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -72,6 +72,22 @@ namespace game const char* name; }; + struct point + { + float x; + float y; + float f2; + float f3; + }; + + struct rectangle + { + point p0; + point p1; + point p2; + point p3; + }; + struct Glyph { unsigned short letter; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index a740d9a8..b0d0cd4f 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -83,6 +83,7 @@ namespace game float angle, float* color, Material* material)> R_AddCmdDrawStretchPicRotateXY{0x3C99B0}; WEAK symbol R_AddCmdDrawText{0x76C660}; + WEAK symbol R_DrawRectangle{0x76A280}; WEAK symbol R_AddCmdDrawTextWithCursor{0x76CAF0}; WEAK symbol R_RegisterFont{0x746FE0}; @@ -102,6 +103,7 @@ namespace game WEAK symbol Sys_IsDatabaseReady2{0x5A9FE0}; WEAK symbol UI_SafeTranslateString{0x5A2930}; + WEAK symbol UI_PlayLocalSoundAlias{0x606080}; WEAK symbol longjmp{0x89EED0}; WEAK symbol _setjmp{0x8EC2E0}; diff --git a/src/client/game/ui_scripting/element.cpp b/src/client/game/ui_scripting/element.cpp index 156b8fa0..9a917965 100644 --- a/src/client/game/ui_scripting/element.cpp +++ b/src/client/game/ui_scripting/element.cpp @@ -10,6 +10,23 @@ namespace ui_scripting namespace { uint64_t next_id; + float screen_max[2]; + + struct point + { + float x; + float y; + float f2; + float f3; + }; + + struct rectangle + { + point p0; + point p1; + point p2; + point p3; + }; std::unordered_map font_map = { @@ -43,6 +60,49 @@ namespace ui_scripting return 0.f; } } + + void draw_image(float x, float y, float w, float h, float* transform, float* color, game::Material* material) + { + game::rectangle rect; + + rect.p0.x = x; + rect.p0.y = y; + rect.p0.f2 = 0.f; + rect.p0.f3 = 1.f; + + rect.p1.x = x + w; + rect.p1.y = y; + rect.p1.f2 = 0.f; + rect.p1.f3 = 1.f; + + rect.p2.x = x + w; + rect.p2.y = y + h; + rect.p2.f2 = 0.f; + rect.p2.f3 = 1.f; + + rect.p3.x = x; + rect.p3.y = y + h; + rect.p3.f2 = 0.f; + rect.p3.f3 = 1.f; + + game::R_DrawRectangle(&rect, transform[0], transform[1], transform[2], transform[3], color, material); + } + + void check_resize() + { + screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0]; + screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1]; + } + + float relative(float value) + { + return (value / 1920.f) * screen_max[0]; + } + + int relative(int value) + { + return (int)(((float)value / 1920.f) * screen_max[0]); + } } element::element() @@ -163,6 +223,14 @@ namespace ui_scripting this->border_width[3] = left; } + void element::set_slice(float left_percent, float top_percent, float right_percent, float bottom_percent) + { + this->slice[0] = left_percent; + this->slice[1] = top_percent; + this->slice[2] = right_percent; + this->slice[3] = bottom_percent; + } + void element::set_material(const std::string& _material) { this->material = _material; @@ -178,69 +246,72 @@ namespace ui_scripting void element::render() const { + check_resize(); + const auto background_material = game::Material_RegisterHandle(this->material.data()); - game::R_AddCmdDrawStretchPic( - this->x + this->border_width[3], - this->y + this->border_width[0], - this->w, this->h, - 0.0f, 0.0f, 0.0f, 0.0f, + draw_image( + relative(this->x) + relative(this->border_width[3]), + relative(this->y) + relative(this->border_width[0]), + relative(this->w), + relative(this->h), + (float*)this->slice, (float*)this->background_color, background_material ); const auto _border_material = game::Material_RegisterHandle(this->border_material.data()); - game::R_AddCmdDrawStretchPic( - this->x, - this->y, - this->w + this->border_width[1] + this->border_width[3], - this->border_width[0], - 0.f, 0.f, 0.f, 0.f, - (float*)this->border_color, - _border_material - ); - - game::R_AddCmdDrawStretchPic( - this->x + this->border_width[3] + this->w, - this->y + this->border_width[0], - this->border_width[1], - this->h, - 0.f, 0.f, 0.f, 0.f, + draw_image( + relative(this->x), + relative(this->y), + relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]), + relative(this->border_width[0]), + (float*)this->slice, (float*)this->border_color, _border_material ); - game::R_AddCmdDrawStretchPic( - this->x, - this->y + this->h + this->border_width[0], - this->w + this->border_width[1] + this->border_width[3], - this->border_width[2], - 0.f, 0.f, 0.f, 0.f, + draw_image( + relative(this->x) + relative(this->border_width[3]) + relative(this->w), + relative(this->y) + relative(this->border_width[0]), + relative(this->border_width[1]), + relative(this->h), + (float*)this->slice, (float*)this->border_color, _border_material ); - game::R_AddCmdDrawStretchPic( - this->x, - this->y + this->border_width[0], - this->border_width[3], - this->h, - 0.f, 0.f, 0.f, 0.f, + draw_image( + relative(this->x), + relative(this->y) + relative(this->h) + relative(this->border_width[0]), + relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]), + relative(this->border_width[2]), + (float*)this->slice, + (float*)this->border_color, + _border_material + ); + + draw_image( + relative(this->x), + relative(this->y) + relative(this->border_width[0]), + relative(this->border_width[3]), + relative(this->h), + (float*)this->slice, (float*)this->border_color, _border_material ); if (!this->text.empty()) { - const auto _font = game::R_RegisterFont(this->font.data(), this->fontsize); + const auto _font = game::R_RegisterFont(this->font.data(), relative(this->fontsize)); const auto text_width = game::R_TextWidth(this->text.data(), 0x7FFFFFFF, _font); - auto _horzalign = get_align_value(this->horzalign, (float)text_width, this->w); - auto _vertalign = get_align_value(this->vertalign, (float)this->fontsize, this->h); + auto _horzalign = get_align_value(this->horzalign, (float)text_width, relative(this->w)); + auto _vertalign = get_align_value(this->vertalign, (float)relative(this->fontsize), relative(this->h)); game::R_AddCmdDrawText(this->text.data(), 0x7FFFFFFF, _font, - this->x + this->text_offset[0] + _horzalign + this->border_width[3], - this->y + this->text_offset[1] + _vertalign + this->fontsize + this->border_width[0], + relative(this->x) + relative(this->text_offset[0]) + _horzalign + relative(this->border_width[3]), + relative(this->y) + relative(this->text_offset[1]) + _vertalign + relative(this->fontsize) + relative(this->border_width[0]), 1.0f, 1.0f, 0.0f, (float*)this->color, 0 diff --git a/src/client/game/ui_scripting/element.hpp b/src/client/game/ui_scripting/element.hpp index 2d9bd110..f2402aa9 100644 --- a/src/client/game/ui_scripting/element.hpp +++ b/src/client/game/ui_scripting/element.hpp @@ -33,6 +33,8 @@ namespace ui_scripting void set_border_width(float top, float right, float bottom); void set_border_width(float top, float right, float bottom, float left); + void set_slice(float left_percent, float top_percent, float right_percent, float bottom_percent); + void set_rect(const float _x, const float _y, const float _w, const float _h); uint64_t id; @@ -51,6 +53,7 @@ namespace ui_scripting float background_color[4] = {0.f, 0.f, 0.f, 0.f}; float border_color[4] = {0.f, 0.f, 0.f, 0.f}; float border_width[4] = {0.f, 0.f, 0.f, 0.f}; + float slice[4] = {0.f, 0.f, 1.f, 1.f}; alignment horzalign = alignment::start; alignment vertalign = alignment::start; diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 93d7cf5a..3004e908 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -17,6 +17,28 @@ namespace ui_scripting::lua std::unordered_map menus; std::vector elements; element ui_element; + float screen_max[2]; + + void check_resize() + { + screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0]; + screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1]; + } + + int relative_mouse(int value) + { + return (int)(((float)value / screen_max[0]) * 1920.f); + } + + int relative(int value) + { + return (int)(((float)value / 1920.f) * screen_max[0]); + } + + float relative(float value) + { + return (value / 1920.f) * screen_max[0]; + } scripting::script_value convert(const sol::lua_value& value) { @@ -64,6 +86,8 @@ namespace ui_scripting::lua void render_menus() { + check_resize(); + for (const auto& menu : menus) { if (menu.second.visible) @@ -483,6 +507,7 @@ namespace ui_scripting::lua static_cast(&element::set_border_width) ); element_type["settextoffset"] = &element::set_text_offset; + element_type["setslice"] = &element::set_slice; element_type["onnotify"] = [&handler](element& element, const std::string& event, const event_callback& callback) @@ -704,6 +729,22 @@ namespace ui_scripting::lua ::game::R_AddCmdDrawStretchPic(x, y, width, height, s0, t0, s1, t1, _color, _material); }; + game_type["playsound"] = [](const game&, const std::string& sound) + { + ::game::UI_PlayLocalSoundAlias(0, sound.data()); + }; + + game_type["getwindowsize"] = [](const game&, const sol::this_state s) + { + const auto size = ::game::ScrPlace_GetViewPlacement()->realViewportSize; + + auto screen = sol::table::create(s.lua_state()); + screen["x"] = size[0]; + screen["y"] = size[1]; + + return screen; + }; + struct player { }; @@ -787,8 +828,6 @@ namespace ui_scripting::lua void open_menu(const std::string& name) { - printf("open_menu\n"); - if (menus.find(name) == menus.end()) { return; @@ -799,8 +838,6 @@ namespace ui_scripting::lua void close_menu(const std::string& name) { - printf("close_menu\n"); - if (menus.find(name) == menus.end()) { return; @@ -874,7 +911,8 @@ namespace ui_scripting::lua if (type == "mousemove") { - handle_mousemove_event(this->state_, this->event_handler_, arguments[0], arguments[1]); + handle_mousemove_event(this->state_, this->event_handler_, + relative_mouse(arguments[0]), relative_mouse(arguments[1])); } } diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index 0e0ff93a..6ce9656a 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -24,8 +24,6 @@ namespace ui_scripting::lua::engine return; } - printf("here\n"); - const auto scripts = utils::io::list_files(script_dir); for (const auto& script : scripts) From 15474f18d0b06fe9a1747b28031b8edf41d9a355 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 12 Sep 2021 02:29:17 +0200 Subject: [PATCH 03/11] Update element.cpp --- src/client/game/ui_scripting/element.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/game/ui_scripting/element.cpp b/src/client/game/ui_scripting/element.cpp index 9a917965..0f620856 100644 --- a/src/client/game/ui_scripting/element.cpp +++ b/src/client/game/ui_scripting/element.cpp @@ -313,8 +313,7 @@ namespace ui_scripting relative(this->x) + relative(this->text_offset[0]) + _horzalign + relative(this->border_width[3]), relative(this->y) + relative(this->text_offset[1]) + _vertalign + relative(this->fontsize) + relative(this->border_width[0]), 1.0f, 1.0f, 0.0f, - (float*)this->color, - 0 + (float*)this->color, 0 ); } } From 0184920bb5800454d0a899927cfe47914c9ff5e1 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Sun, 12 Sep 2021 02:39:43 +0200 Subject: [PATCH 04/11] Menu overlay type --- src/client/game/ui_scripting/lua/context.cpp | 17 +++++++++++++++-- src/client/game/ui_scripting/menu.hpp | 9 +++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 3004e908..d5f1f9eb 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -84,13 +84,18 @@ namespace ui_scripting::lua return (px > x && px < x + w && py > y && py < y + h); } + bool is_menu_visible(const menu& menu) + { + return menu.visible || (menu.type == menu_type::overlay && game::Menu_IsMenuOpenAndVisible(0, menu.overlay_menu.data())); + } + void render_menus() { check_resize(); for (const auto& menu : menus) { - if (menu.second.visible) + if (is_menu_visible(menu.second)) { menu.second.render(); } @@ -103,7 +108,7 @@ namespace ui_scripting::lua for (const auto& menu : menus) { - if (!menu.second.visible) + if (!is_menu_visible(menu.second)) { continue; } @@ -574,6 +579,14 @@ namespace ui_scripting::lua return &menus[name]; }; + game_type["newmenuoverlay"] = [](const sol::lua_value&, const std::string& name, const std::string& menu_name) + { + menus[name] = {}; + menus[name].type = menu_type::overlay; + menus[name].overlay_menu = menu_name; + return &menus[name]; + }; + game_type["getmouseposition"] = [](const sol::this_state s, const game&) { auto pos = sol::table::create(s.lua_state()); diff --git a/src/client/game/ui_scripting/menu.hpp b/src/client/game/ui_scripting/menu.hpp index b8936bf7..70121050 100644 --- a/src/client/game/ui_scripting/menu.hpp +++ b/src/client/game/ui_scripting/menu.hpp @@ -4,6 +4,12 @@ namespace ui_scripting { + enum menu_type + { + normal, + overlay + }; + class menu final { public: @@ -19,6 +25,9 @@ namespace ui_scripting void add_child(element* el); void render() const; + menu_type type = normal; + + std::string overlay_menu; std::vector children{}; }; } From 4311c52176e248a9ef66cb5af6ebdbaa2e55320b Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Mon, 13 Sep 2021 01:30:14 +0200 Subject: [PATCH 05/11] More UI scripting progress --- src/client/component/ui_scripting.cpp | 5 +- src/client/game/ui_scripting/element.cpp | 21 +- src/client/game/ui_scripting/element.hpp | 1 + src/client/game/ui_scripting/lua/context.cpp | 671 +++++++----------- src/client/game/ui_scripting/lua/context.hpp | 8 +- src/client/game/ui_scripting/lua/engine.cpp | 345 ++++++++- src/client/game/ui_scripting/lua/engine.hpp | 3 + src/client/game/ui_scripting/lua/event.hpp | 2 +- .../game/ui_scripting/lua/event_handler.cpp | 86 ++- .../game/ui_scripting/lua/event_handler.hpp | 7 +- .../game/ui_scripting/lua/scheduler.cpp | 55 +- .../game/ui_scripting/lua/scheduler.hpp | 4 + src/client/resource.hpp | 2 + src/client/resource.rc | 2 + src/client/resources/animation.lua | 87 +++ 15 files changed, 868 insertions(+), 431 deletions(-) create mode 100644 src/client/resources/animation.lua diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 613dedb0..06ad4421 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -10,7 +10,6 @@ #include "ui_scripting.hpp" #include "game/ui_scripting/lua/engine.hpp" -#include "game/ui_scripting/lua/context.hpp" #include @@ -46,7 +45,7 @@ namespace ui_scripting const std::string name = params.get(1); scheduler::once([name]() { - ui_scripting::lua::open_menu(name); + ui_scripting::lua::engine::open_menu(name); }, scheduler::pipeline::renderer); }); @@ -55,7 +54,7 @@ namespace ui_scripting const std::string name = params.get(1); scheduler::once([name]() { - ui_scripting::lua::close_menu(name); + ui_scripting::lua::engine::close_menu(name); }, scheduler::pipeline::renderer); }); } diff --git a/src/client/game/ui_scripting/element.cpp b/src/client/game/ui_scripting/element.cpp index 0f620856..826d9281 100644 --- a/src/client/game/ui_scripting/element.cpp +++ b/src/client/game/ui_scripting/element.cpp @@ -148,11 +148,25 @@ namespace ui_scripting if (font_map.find(lowercase) == font_map.end()) { - this->font = font_map["default"]; + this->font = "default"; } else { - this->font = font_map[lowercase]; + this->font = lowercase; + } + } + + void element::set_font(const std::string& _font) + { + const auto lowercase = utils::string::to_lower(_font); + + if (font_map.find(lowercase) == font_map.end()) + { + this->font = "default"; + } + else + { + this->font = lowercase; } } @@ -303,7 +317,8 @@ namespace ui_scripting if (!this->text.empty()) { - const auto _font = game::R_RegisterFont(this->font.data(), relative(this->fontsize)); + const auto fontname = font_map[this->font]; + const auto _font = game::R_RegisterFont(fontname.data(), relative(this->fontsize)); const auto text_width = game::R_TextWidth(this->text.data(), 0x7FFFFFFF, _font); auto _horzalign = get_align_value(this->horzalign, (float)text_width, relative(this->w)); diff --git a/src/client/game/ui_scripting/element.hpp b/src/client/game/ui_scripting/element.hpp index f2402aa9..1820e432 100644 --- a/src/client/game/ui_scripting/element.hpp +++ b/src/client/game/ui_scripting/element.hpp @@ -19,6 +19,7 @@ namespace ui_scripting void set_vertalign(const std::string& value); void set_text(const std::string& text); + void set_font(const std::string& _font); void set_font(const std::string& _font, const int _fontsize); void set_color(float r, float g, float b, float a); void set_text_offset(float x, float y); diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index d5f1f9eb..6ee57f9a 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -9,36 +9,18 @@ #include "component/scheduler.hpp" #include +#include namespace ui_scripting::lua { + std::unordered_map menus; + std::vector elements; + element ui_element; + int mouse[2]; + namespace { - std::unordered_map menus; - std::vector elements; - element ui_element; - float screen_max[2]; - - void check_resize() - { - screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0]; - screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1]; - } - - int relative_mouse(int value) - { - return (int)(((float)value / screen_max[0]) * 1920.f); - } - - int relative(int value) - { - return (int)(((float)value / 1920.f) * screen_max[0]); - } - - float relative(float value) - { - return (value / 1920.f) * screen_max[0]; - } + const auto animation_script = utils::nt::load_resource(LUA_ANIMATION_SCRIPT); scripting::script_value convert(const sol::lua_value& value) { @@ -79,286 +61,6 @@ namespace ui_scripting::lua return {}; } - bool point_in_rect(int px, int py, int x, int y, int w, int h) - { - return (px > x && px < x + w && py > y && py < y + h); - } - - bool is_menu_visible(const menu& menu) - { - return menu.visible || (menu.type == menu_type::overlay && game::Menu_IsMenuOpenAndVisible(0, menu.overlay_menu.data())); - } - - void render_menus() - { - check_resize(); - - for (const auto& menu : menus) - { - if (is_menu_visible(menu.second)) - { - menu.second.render(); - } - } - } - - std::vector elements_in_point(int x, int y) - { - std::vector result; - - for (const auto& menu : menus) - { - if (!is_menu_visible(menu.second)) - { - continue; - } - - for (const auto& child : menu.second.children) - { - const auto in_rect = point_in_rect( - x, y, - (int)child->x, - (int)child->y, - (int)child->w + (int)child->border_width[1] + (int)child->border_width[3], - (int)child->h + (int)child->border_width[0] + (int)child->border_width[2] - ); - - if (in_rect) - { - result.push_back(child); - } - } - } - - return result; - } - - int mouse[2]; - - void handle_key_event(sol::state& state, event_handler& handler, const int key, const int down) - { - const auto _elements = elements_in_point(mouse[0], mouse[1]); - - switch (key) - { - case game::K_MOUSE2: - case game::K_MOUSE1: - { - const auto click_name = key == game::K_MOUSE1 - ? "click" - : "rightclick"; - - const auto key_name = key == game::K_MOUSE1 - ? "mouse" - : "rightmouse"; - - { - event main_event; - main_event.element = &ui_element; - main_event.name = utils::string::va("%s%s", key_name, down ? "down" : "up"); - main_event.arguments = - { - {state, mouse[0]}, - {state, mouse[1]}, - }; - - handler.dispatch(main_event); - - for (const auto& element : _elements) - { - event event; - event.element = element; - event.name = utils::string::va("%s%s", key_name, down ? "down" : "up"); - event.arguments = - { - {state, mouse[0]}, - {state, mouse[1]}, - }; - - handler.dispatch(event); - } - } - - if (!down) - { - event main_event; - main_event.element = &ui_element; - main_event.name = click_name; - main_event.arguments = - { - {state, mouse[0]}, - {state, mouse[1]}, - }; - - handler.dispatch(main_event); - - for (const auto& element : _elements) - { - event event; - event.element = element; - event.name = click_name; - event.arguments = - { - {state, mouse[0]}, - {state, mouse[1]}, - }; - - handler.dispatch(event); - } - } - - break; - } - case game::K_MWHEELUP: - case game::K_MWHEELDOWN: - { - const auto key_name = key == game::K_MWHEELUP - ? "scrollup" - : "scrolldown"; - - if (!down) - { - break; - } - - { - event main_event; - main_event.element = &ui_element; - main_event.name = key_name; - main_event.arguments = - { - {state, mouse[0]}, - {state, mouse[1]}, - }; - - handler.dispatch(main_event); - - for (const auto& element : _elements) - { - event event; - event.element = element; - event.name = key_name; - event.arguments = - { - {state, mouse[0]}, - {state, mouse[1]}, - }; - - handler.dispatch(event); - } - } - - break; - } - default: - { - event event; - event.element = &ui_element; - event.name = down - ? "keydown" - : "keyup"; - event.arguments = - { - {state, key}, - }; - - handler.dispatch(event); - - break; - } - } - } - - void handle_char_event(sol::state& state, event_handler& handler, const int key) - { - std::string key_str = {(char)key}; - event event; - event.element = &ui_element; - event.name = "keypress"; - event.arguments = - { - {state, key_str}, - }; - - handler.dispatch(event); - } - - std::vector previous_elements; - void handle_mousemove_event(sol::state& state, event_handler& handler, const int x, const int y) - { - mouse[0] = x; - mouse[1] = y; - - { - event event; - event.element = &ui_element; - event.name = "mousemove"; - event.arguments = - { - {state, x}, - {state, y}, - }; - - handler.dispatch(event); - } - - const auto _elements = elements_in_point(x, y); - for (const auto& element : _elements) - { - event event; - event.element = element; - event.name = "mouseover"; - - handler.dispatch(event); - } - - for (const auto& element : previous_elements) - { - auto found = false; - - for (const auto& _element : _elements) - { - if (element == _element) - { - found = true; - } - } - - if (!found) - { - event event; - event.element = element; - event.name = "mouseleave"; - - handler.dispatch(event); - } - } - - for (const auto& element : _elements) - { - auto found = false; - - for (const auto& _element : previous_elements) - { - if (element == _element) - { - found = true; - } - } - - if (!found) - { - event event; - event.element = element; - event.name = "mouseenter"; - - handler.dispatch(event); - } - } - - previous_elements = _elements; - } - bool valid_dvar_name(const std::string& name) { for (const auto c : name) @@ -372,22 +74,9 @@ namespace ui_scripting::lua return true; } - void clear_menus() - { - menus.clear(); - for (const auto element : elements) - { - delete element; - } - - elements.clear(); - } - void setup_types(sol::state& state, event_handler& handler, scheduler& scheduler) { - clear_menus(); - - auto vector_type = state.new_usertype("scripting::vector", sol::constructors()); + auto vector_type = state.new_usertype("vector", sol::constructors()); vector_type["x"] = sol::property(&scripting::vector::get_x, &scripting::vector::set_x); vector_type["y"] = sol::property(&scripting::vector::get_y, &scripting::vector::set_y); vector_type["z"] = sol::property(&scripting::vector::get_z, &scripting::vector::set_z); @@ -398,78 +87,78 @@ namespace ui_scripting::lua vector_type[sol::meta_function::addition] = sol::overload( [](const scripting::vector& a, const scripting::vector& b) - { - return scripting::vector( - a.get_x() + b.get_x(), - a.get_y() + b.get_y(), - a.get_z() + b.get_z() - ); - }, + { + return scripting::vector( + a.get_x() + b.get_x(), + a.get_y() + b.get_y(), + a.get_z() + b.get_z() + ); + }, [](const scripting::vector& a, const int value) - { - return scripting::vector( - a.get_x() + value, - a.get_y() + value, - a.get_z() + value - ); - } + { + return scripting::vector( + a.get_x() + value, + a.get_y() + value, + a.get_z() + value + ); + } ); vector_type[sol::meta_function::subtraction] = sol::overload( [](const scripting::vector& a, const scripting::vector& b) - { - return scripting::vector( - a.get_x() - b.get_x(), - a.get_y() - b.get_y(), - a.get_z() - b.get_z() - ); - }, + { + return scripting::vector( + a.get_x() - b.get_x(), + a.get_y() - b.get_y(), + a.get_z() - b.get_z() + ); + }, [](const scripting::vector& a, const int value) - { - return scripting::vector( - a.get_x() - value, - a.get_y() - value, - a.get_z() - value - ); - } + { + return scripting::vector( + a.get_x() - value, + a.get_y() - value, + a.get_z() - value + ); + } ); vector_type[sol::meta_function::multiplication] = sol::overload( [](const scripting::vector& a, const scripting::vector& b) - { - return scripting::vector( - a.get_x() * b.get_x(), - a.get_y() * b.get_y(), - a.get_z() * b.get_z() - ); - }, + { + return scripting::vector( + a.get_x() * b.get_x(), + a.get_y() * b.get_y(), + a.get_z() * b.get_z() + ); + }, [](const scripting::vector& a, const int value) - { - return scripting::vector( - a.get_x() * value, - a.get_y() * value, - a.get_z() * value - ); - } + { + return scripting::vector( + a.get_x() * value, + a.get_y() * value, + a.get_z() * value + ); + } ); vector_type[sol::meta_function::division] = sol::overload( [](const scripting::vector& a, const scripting::vector& b) - { - return scripting::vector( - a.get_x() / b.get_x(), - a.get_y() / b.get_y(), - a.get_z() / b.get_z() - ); - }, + { + return scripting::vector( + a.get_x() / b.get_x(), + a.get_y() / b.get_y(), + a.get_z() / b.get_z() + ); + }, [](const scripting::vector& a, const int value) - { - return scripting::vector( - a.get_x() / value, - a.get_y() / value, - a.get_z() / value - ); - } + { + return scripting::vector( + a.get_x() / value, + a.get_y() / value, + a.get_z() / value + ); + } ); vector_type[sol::meta_function::equal_to] = [](const scripting::vector& a, const scripting::vector& b) @@ -498,8 +187,11 @@ namespace ui_scripting::lua element_type["setvertalign"] = &element::set_vertalign; element_type["sethorzalign"] = &element::set_horzalign; - element_type["setrect"] =&element::set_rect; - element_type["setfont"] = &element::set_font; + element_type["setrect"] = &element::set_rect; + element_type["setfont"] = sol::overload( + static_cast(&element::set_font), + static_cast(&element::set_font) + ); element_type["settext"] = &element::set_text; element_type["setmaterial"] = &element::set_material; element_type["setcolor"] = &element::set_color; @@ -537,6 +229,148 @@ namespace ui_scripting::lua return rect; }; + element_type["x"] = sol::property( + [](element& element) + { + return element.x; + }, + [](element& element, float x) + { + element.x = x; + } + ); + + element_type["y"] = sol::property( + [](element& element) + { + return element.y; + }, + [](element& element, float y) + { + element.y = y; + } + ); + + element_type["w"] = sol::property( + [](element& element) + { + return element.w; + }, + [](element& element, float w) + { + element.w = w; + } + ); + + element_type["h"] = sol::property( + [](element& element) + { + return element.h; + }, + [](element& element, float h) + { + element.h = h; + } + ); + + element_type["color"] = sol::property( + [](element& element, const sol::this_state s) + { + auto color = sol::table::create(s.lua_state()); + color["r"] = element.color[0]; + color["g"] = element.color[1]; + color["b"] = element.color[2]; + color["a"] = element.color[3]; + return color; + }, + [](element& element, const sol::lua_table color) + { + element.color[0] = color["r"].get_type() == sol::type::number ? color["r"].get() : 0.f; + element.color[1] = color["g"].get_type() == sol::type::number ? color["g"].get() : 0.f; + element.color[2] = color["b"].get_type() == sol::type::number ? color["b"].get() : 0.f; + element.color[3] = color["a"].get_type() == sol::type::number ? color["a"].get() : 0.f; + } + ); + + element_type["backcolor"] = sol::property( + [](element& element, const sol::this_state s) + { + auto color = sol::table::create(s.lua_state()); + color["r"] = element.background_color[0]; + color["g"] = element.background_color[1]; + color["b"] = element.background_color[2]; + color["a"] = element.background_color[3]; + return color; + }, + [](element& element, const sol::lua_table color) + { + element.background_color[0] = color["r"].get_type() == sol::type::number ? color["r"].get() : 0.f; + element.background_color[1] = color["g"].get_type() == sol::type::number ? color["g"].get() : 0.f; + element.background_color[2] = color["b"].get_type() == sol::type::number ? color["b"].get() : 0.f; + element.background_color[3] = color["a"].get_type() == sol::type::number ? color["a"].get() : 0.f; + } + ); + + element_type["bordercolor"] = sol::property( + [](element& element, const sol::this_state s) + { + auto color = sol::table::create(s.lua_state()); + color["r"] = element.border_color[0]; + color["g"] = element.border_color[1]; + color["b"] = element.border_color[2]; + color["a"] = element.border_color[3]; + return color; + }, + [](element& element, const sol::lua_table color) + { + element.border_color[0] = color["r"].get_type() == sol::type::number ? color["r"].get() : 0.f; + element.border_color[1] = color["g"].get_type() == sol::type::number ? color["g"].get() : 0.f; + element.border_color[2] = color["b"].get_type() == sol::type::number ? color["b"].get() : 0.f; + element.border_color[3] = color["a"].get_type() == sol::type::number ? color["a"].get() : 0.f; + } + ); + + element_type["borderwidth"] = sol::property( + [](element& element, const sol::this_state s) + { + auto color = sol::table::create(s.lua_state()); + color["top"] = element.border_width[0]; + color["right"] = element.border_width[1]; + color["bottom"] = element.border_width[2]; + color["left"] = element.border_width[3]; + return color; + }, + [](element& element, const sol::lua_table color) + { + element.border_width[0] = color["top"].get_type() == sol::type::number ? color["top"].get() : 0.f; + element.border_width[1] = color["right"].get_type() == sol::type::number ? color["right"].get() : element.border_width[1]; + element.border_width[2] = color["bottom"].get_type() == sol::type::number ? color["bottom"].get() : element.border_width[2]; + element.border_width[3] = color["left"].get_type() == sol::type::number ? color["left"].get() : element.border_width[3]; + } + ); + + element_type["font"] = sol::property( + [](element& element) + { + return element.font; + }, + [](element& element, const std::string& font) + { + element.set_font(font); + } + ); + + element_type["fontsize"] = sol::property( + [](element& element) + { + return element.fontsize; + }, + [](element& element, float fontsize) + { + element.fontsize = (int)fontsize; + } + ); + element_type["onnotifyonce"] = [&handler](element& element, const std::string& event, const event_callback& callback) { @@ -549,6 +383,29 @@ namespace ui_scripting::lua return handler.add_event_listener(std::move(listener)); }; + element_type["notify"] = [&handler](element& element, const sol::this_state s, const std::string& _event, + sol::variadic_args va) + { + event event; + event.element = &element; + event.name = _event; + + for (auto arg : va) + { + if (arg.get_type() == sol::type::number) + { + event.arguments.push_back(arg.as()); + } + + if (arg.get_type() == sol::type::string) + { + event.arguments.push_back(arg.as()); + } + } + + handler.dispatch(event); + }; + auto menu_type = state.new_usertype("menu"); menu_type["addchild"] = [](const sol::this_state s, menu& menu, element& element) @@ -573,6 +430,12 @@ namespace ui_scripting::lua auto game_type = state.new_usertype("game_"); state["game"] = game(); + game_type["time"] = []() + { + const auto now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + return now.count(); + }; + game_type["newmenu"] = [](const sol::lua_value&, const std::string& name) { menus[name] = {}; @@ -621,6 +484,18 @@ namespace ui_scripting::lua return scheduler.add(callback, 0, false); }; + 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["onnotify"] = [&handler](const game&, const std::string& event, const event_callback& callback) { @@ -836,29 +711,11 @@ namespace ui_scripting::lua ::game::g_entities[0].client->velocity[1] = velocity.get_y(); ::game::g_entities[0].client->velocity[2] = velocity.get_z(); }; + + state.script(animation_script); } } - void open_menu(const std::string& name) - { - if (menus.find(name) == menus.end()) - { - return; - } - - menus[name].open(); - } - - void close_menu(const std::string& name) - { - if (menus.find(name) == menus.end()) - { - return; - } - - menus[name].close(); - } - context::context(std::string folder) : folder_(std::move(folder)) , scheduler_(state_) @@ -905,28 +762,14 @@ namespace ui_scripting::lua void context::run_frame() { - render_menus(); this->scheduler_.run_frame(); this->state_.collect_garbage(); } - void context::ui_event(const std::string& type, const std::vector& arguments) + void context::notify(const event& e) { - if (type == "key") - { - handle_key_event(this->state_, this->event_handler_, arguments[0], arguments[1]); - } - - if (type == "char") - { - handle_char_event(this->state_, this->event_handler_, arguments[0]); - } - - if (type == "mousemove") - { - handle_mousemove_event(this->state_, this->event_handler_, - relative_mouse(arguments[0]), relative_mouse(arguments[1])); - } + this->scheduler_.dispatch(e); + this->event_handler_.dispatch(e); } void context::load_script(const std::string& script) diff --git a/src/client/game/ui_scripting/lua/context.hpp b/src/client/game/ui_scripting/lua/context.hpp index b2a88f59..7a45e4f3 100644 --- a/src/client/game/ui_scripting/lua/context.hpp +++ b/src/client/game/ui_scripting/lua/context.hpp @@ -14,8 +14,10 @@ namespace ui_scripting::lua { - void open_menu(const std::string& name); - void close_menu(const std::string& name); + extern std::unordered_map menus; + extern std::vector elements; + extern element ui_element; + extern int mouse[2]; class context { @@ -30,7 +32,7 @@ namespace ui_scripting::lua context& operator=(const context&) = delete; void run_frame(); - void ui_event(const std::string&, const std::vector&); + void notify(const event& e); private: sol::state state_{}; diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index 6ce9656a..cdc60b94 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -7,8 +7,284 @@ namespace ui_scripting::lua::engine { + void notify(const event& e); + namespace { + float screen_max[2]; + + void check_resize() + { + screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0]; + screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1]; + } + + int relative_mouse(int value) + { + return (int)(((float)value / screen_max[0]) * 1920.f); + } + + int relative(int value) + { + return (int)(((float)value / 1920.f) * screen_max[0]); + } + + float relative(float value) + { + return (value / 1920.f) * screen_max[0]; + } + + bool point_in_rect(int px, int py, int x, int y, int w, int h) + { + return (px > x && px < x + w && py > y && py < y + h); + } + + bool is_menu_visible(const menu& menu) + { + return menu.visible || (menu.type == menu_type::overlay && game::Menu_IsMenuOpenAndVisible(0, menu.overlay_menu.data())); + } + + std::vector elements_in_point(int x, int y) + { + std::vector result; + + for (const auto& menu : menus) + { + if (!is_menu_visible(menu.second)) + { + continue; + } + + for (const auto& child : menu.second.children) + { + const auto in_rect = point_in_rect( + x, y, + (int)child->x, + (int)child->y, + (int)child->w + (int)child->border_width[1] + (int)child->border_width[3], + (int)child->h + (int)child->border_width[0] + (int)child->border_width[2] + ); + + if (in_rect) + { + result.push_back(child); + } + } + } + + return result; + } + + void handle_key_event(const int key, const int down) + { + const auto _elements = elements_in_point(mouse[0], mouse[1]); + + switch (key) + { + case game::K_MOUSE2: + case game::K_MOUSE1: + { + const auto click_name = key == game::K_MOUSE1 + ? "click" + : "rightclick"; + + const auto key_name = key == game::K_MOUSE1 + ? "mouse" + : "rightmouse"; + + { + event main_event; + main_event.element = &ui_element; + main_event.name = utils::string::va("%s%s", key_name, down ? "down" : "up"); + main_event.arguments = + { + mouse[0], + mouse[1], + }; + + engine::notify(main_event); + + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = utils::string::va("%s%s", key_name, down ? "down" : "up"); + event.arguments = + { + mouse[0], + mouse[1], + }; + + engine::notify(event); + } + } + + if (!down) + { + event main_event; + main_event.element = &ui_element; + main_event.name = click_name; + main_event.arguments = + { + mouse[0], + mouse[1], + }; + + engine::notify(main_event); + + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = click_name; + event.arguments = + { + mouse[0], + mouse[1], + }; + + engine::notify(event); + } + } + + break; + } + case game::K_MWHEELUP: + case game::K_MWHEELDOWN: + { + const auto key_name = key == game::K_MWHEELUP + ? "scrollup" + : "scrolldown"; + + if (!down) + { + break; + } + + { + event main_event; + main_event.element = &ui_element; + main_event.name = key_name; + main_event.arguments = + { + mouse[0], + mouse[1], + }; + + engine::notify(main_event); + + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = key_name; + event.arguments = {mouse[0], mouse[1]}; + + engine::notify(event); + } + } + + break; + } + default: + { + event event; + event.element = &ui_element; + event.name = down + ? "keydown" + : "keyup"; + event.arguments = {key}; + + notify(event); + + break; + } + } + } + + void handle_char_event(const int key) + { + std::string key_str = {(char)key}; + event event; + event.element = &ui_element; + event.name = "keypress"; + event.arguments = {key_str}; + + engine::notify(event); + } + + std::vector previous_elements; + void handle_mousemove_event(const int x, const int y) + { + mouse[0] = x; + mouse[1] = y; + + { + event event; + event.element = &ui_element; + event.name = "mousemove"; + event.arguments = {x, y}; + + engine::notify(event); + } + + const auto _elements = elements_in_point(x, y); + for (const auto& element : _elements) + { + event event; + event.element = element; + event.name = "mouseover"; + + engine::notify(event); + } + + for (const auto& element : previous_elements) + { + auto found = false; + + for (const auto& _element : _elements) + { + if (element == _element) + { + found = true; + } + } + + if (!found) + { + event event; + event.element = element; + event.name = "mouseleave"; + + engine::notify(event); + } + } + + for (const auto& element : _elements) + { + auto found = false; + + for (const auto& _element : previous_elements) + { + if (element == _element) + { + found = true; + } + } + + if (!found) + { + event event; + event.element = element; + event.name = "mouseenter"; + + engine::notify(event); + } + } + + previous_elements = _elements; + } + auto& get_scripts() { static std::vector> scripts{}; @@ -34,29 +310,96 @@ namespace ui_scripting::lua::engine } } } + + void render_menus() + { + check_resize(); + + for (const auto& menu : menus) + { + if (is_menu_visible(menu.second)) + { + menu.second.render(); + } + } + } + + void clear_menus() + { + menus.clear(); + for (const auto element : elements) + { + delete element; + } + + elements.clear(); + } + } + + void open_menu(const std::string& name) + { + if (menus.find(name) == menus.end()) + { + return; + } + + menus[name].open(); + } + + void close_menu(const std::string& name) + { + if (menus.find(name) == menus.end()) + { + return; + } + + menus[name].close(); } void start() { + clear_menus(); get_scripts().clear(); load_scripts(); } void stop() { + clear_menus(); get_scripts().clear(); } void ui_event(const std::string& type, const std::vector& arguments) + { + if (type == "key") + { + handle_key_event(arguments[0], arguments[1]); + } + + if (type == "char") + { + handle_char_event(arguments[0]); + } + + if (type == "mousemove") + { + handle_mousemove_event(relative_mouse(arguments[0]), relative_mouse(arguments[1])); + } + } + + void notify(const event& e) { for (auto& script : get_scripts()) { - script->ui_event(type, arguments); + script->notify(e); } } void run_frame() { + check_resize(); + render_menus(); + for (auto& script : get_scripts()) { script->run_frame(); diff --git a/src/client/game/ui_scripting/lua/engine.hpp b/src/client/game/ui_scripting/lua/engine.hpp index 0d33fccf..f8f6b032 100644 --- a/src/client/game/ui_scripting/lua/engine.hpp +++ b/src/client/game/ui_scripting/lua/engine.hpp @@ -5,6 +5,9 @@ namespace ui_scripting::lua::engine void start(); void stop(); + void close_menu(const std::string& name); + void open_menu(const std::string& name); + void ui_event(const std::string&, const std::vector&); void run_frame(); } diff --git a/src/client/game/ui_scripting/lua/event.hpp b/src/client/game/ui_scripting/lua/event.hpp index a90fa6d8..428a1eda 100644 --- a/src/client/game/ui_scripting/lua/event.hpp +++ b/src/client/game/ui_scripting/lua/event.hpp @@ -8,6 +8,6 @@ namespace ui_scripting::lua { std::string name; element* element{}; - std::vector arguments; + std::vector> arguments; }; } diff --git a/src/client/game/ui_scripting/lua/event_handler.cpp b/src/client/game/ui_scripting/lua/event_handler.cpp index 86947d65..73fbf336 100644 --- a/src/client/game/ui_scripting/lua/event_handler.cpp +++ b/src/client/game/ui_scripting/lua/event_handler.cpp @@ -1,6 +1,7 @@ #include "std_include.hpp" #include "context.hpp" #include "error.hpp" +#include "../../scripting/lua/value_conversion.hpp" #include "event_handler.hpp" @@ -15,13 +16,22 @@ namespace ui_scripting::lua { this->remove(handle); }; + + event_listener_handle_type["endon"] = [this](const event_listener_handle& handle, const element* 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();) { @@ -33,7 +43,13 @@ namespace ui_scripting::lua if (!i->is_deleted) { - handle_error(i->callback(sol::as_args(event.arguments))); + 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) @@ -62,6 +78,27 @@ namespace ui_scripting::lua return {id}; } + void event_handler::add_endon_condition(const event_listener_handle& handle, const element* element, + const std::string& event) + { + auto merger = [&](task_list& tasks) + { + for (auto& task : tasks) + { + if (task.id == handle.id) + { + task.endon_conditions.emplace_back(element->id, event); + } + } + }; + + callbacks_.access([&](task_list& tasks) + { + merger(tasks); + new_callbacks_.access(merger); + }); + } + void event_handler::clear() { callbacks_.access([&](task_list& tasks) @@ -99,9 +136,54 @@ namespace ui_scripting::lua new_callbacks_.access([&](task_list& new_tasks) { tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), - std::move_iterator(new_tasks.end())); + 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.element->id && 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) + { + const auto index = argument.index(); + + if (index == 0) + { + const sol::lua_value value = {this->state_, std::get(argument)}; + arguments.emplace_back(value); + } + + if (index == 1) + { + const sol::lua_value value = {this->state_, std::get(argument)}; + arguments.emplace_back(value); + } + + } + + return arguments; + } } diff --git a/src/client/game/ui_scripting/lua/event_handler.hpp b/src/client/game/ui_scripting/lua/event_handler.hpp index 4041b432..88eb657a 100644 --- a/src/client/game/ui_scripting/lua/event_handler.hpp +++ b/src/client/game/ui_scripting/lua/event_handler.hpp @@ -1,6 +1,5 @@ #pragma once #include -#include "../element.hpp" namespace ui_scripting::lua { @@ -21,6 +20,7 @@ namespace ui_scripting::lua event_callback callback = {}; bool is_volatile = false; bool is_deleted = false; + std::vector> endon_conditions{}; }; class event_handler final @@ -50,5 +50,10 @@ namespace ui_scripting::lua 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 element* element, const std::string& event); + + event_arguments build_arguments(const event& event) const; }; } diff --git a/src/client/game/ui_scripting/lua/scheduler.cpp b/src/client/game/ui_scripting/lua/scheduler.cpp index b40d191a..973adcec 100644 --- a/src/client/game/ui_scripting/lua/scheduler.cpp +++ b/src/client/game/ui_scripting/lua/scheduler.cpp @@ -12,6 +12,35 @@ namespace ui_scripting::lua { this->remove(handle); }; + + task_handle_type["endon"] = [this](const task_handle& handle, const element* element, const std::string& event) + { + this->add_endon_condition(handle, element, 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.element->id && condition.second == event.name) + { + task.is_deleted = true; + break; + } + } + } + }; + + callbacks_.access([&](task_list& tasks) + { + deleter(tasks); + new_callbacks_.access(deleter); + }); } void scheduler::run_frame() @@ -63,13 +92,13 @@ namespace ui_scripting::lua } task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds, - const bool is_volatile) + 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 bool is_volatile) { const uint64_t id = ++this->current_task_id_; @@ -89,6 +118,26 @@ namespace ui_scripting::lua return {id}; } + void scheduler::add_endon_condition(const task_handle& handle, const element* element, const std::string& event) + { + auto merger = [&](task_list& tasks) + { + for (auto& task : tasks) + { + if (task.id == handle.id) + { + task.endon_conditions.emplace_back(element->id, 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) @@ -114,7 +163,7 @@ namespace ui_scripting::lua new_callbacks_.access([&](task_list& new_tasks) { tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), - std::move_iterator(new_tasks.end())); + std::move_iterator(new_tasks.end())); new_tasks = {}; }); }); diff --git a/src/client/game/ui_scripting/lua/scheduler.hpp b/src/client/game/ui_scripting/lua/scheduler.hpp index 1935e25e..ac730a03 100644 --- a/src/client/game/ui_scripting/lua/scheduler.hpp +++ b/src/client/game/ui_scripting/lua/scheduler.hpp @@ -19,6 +19,7 @@ namespace ui_scripting::lua std::chrono::milliseconds delay{}; bool is_volatile = false; bool is_deleted = false; + std::vector> endon_conditions{}; }; class scheduler final @@ -32,6 +33,7 @@ namespace ui_scripting::lua scheduler(const scheduler&) = delete; scheduler& operator=(const scheduler&) = delete; + void dispatch(const event& event); void run_frame(); void clear(); @@ -44,6 +46,8 @@ namespace ui_scripting::lua utils::concurrency::container callbacks_; std::atomic_int64_t current_task_id_ = 0; + void add_endon_condition(const task_handle& handle, const element* element, const std::string& event); + void remove(const task_handle& handle); void merge_callbacks(); }; diff --git a/src/client/resource.hpp b/src/client/resource.hpp index c2ce9b6a..50ce9bb4 100644 --- a/src/client/resource.hpp +++ b/src/client/resource.hpp @@ -20,3 +20,5 @@ #define RUNNER 312 #define ICON_IMAGE 313 + +#define LUA_ANIMATION_SCRIPT 314 diff --git a/src/client/resource.rc b/src/client/resource.rc index 1b461518..ce4e7c05 100644 --- a/src/client/resource.rc +++ b/src/client/resource.rc @@ -97,6 +97,8 @@ ID_ICON ICON "resources/icon.ico" MENU_MAIN RCDATA "resources/main.html" +LUA_ANIMATION_SCRIPT RCDATA "resources/animation.lua" + #ifdef _DEBUG TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll" #else diff --git a/src/client/resources/animation.lua b/src/client/resources/animation.lua new file mode 100644 index 00000000..3bff9bea --- /dev/null +++ b/src/client/resources/animation.lua @@ -0,0 +1,87 @@ +function element:animate(state, animationtime) + self:notify("cancel_animation") + + local doanimation = function() + local start = { + x = self.x, + y = self.y, + w = self.w, + h = self.h, + color = self.color, + backcolor = self.backcolor, + bordercolor = self.bordercolor, + borderwidth = self.borderwidth, + fontsize = self.fontsize + } + + local _end = {} + for k, v in pairs(start) do + _end[k] = state[k] or v + end + + local diffs = {} + for k, v in pairs(_end) do + if (type(v) == "table") then + local value = {} + + for _k, _v in pairs(v) do + value[_k] = _v - start[k][_k] + end + + diffs[k] = value + else + diffs[k] = v - start[k] + end + end + + local timeout = nil + local interval = nil + local starttime = game:time() + + interval = game:onframe(function() + local time = game:time() + local percentage = (time - starttime) / animationtime + if (percentage >= 1) then + for k, v in pairs(_end) do + self[k] = v + end + return + end + + for k, v in pairs(diffs) do + if (type(v) == "table") then + local value = {} + + for _k, _v in pairs(v) do + value[_k] = start[k][_k] + _v * percentage + end + + self[k] = value + else + self[k] = start[k] + v * percentage + end + end + end) + + timeout = game:ontimeout(function() + interval:clear() + for k, v in pairs(_end) do + self[k] = v + end + end, animationtime) + + self:onnotifyonce("cancel_animation", function() + timeout:clear() + interval:clear() + end) + end + + game:ontimeout(doanimation, 0) +end + +function element:cancelanimations(callback) + self:notify("cancel_animation") + if (type(callback) == "function") then + game:ontimeout(callback, 0) + end +end \ No newline at end of file From 7eb372c539e9a8ab1da214123cb2282a35bdf6fb Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Mon, 13 Sep 2021 01:57:25 +0200 Subject: [PATCH 06/11] Fix debug build --- premake5.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/premake5.lua b/premake5.lua index a2736dc1..eb07c0f6 100644 --- a/premake5.lua +++ b/premake5.lua @@ -263,7 +263,7 @@ flags {"FatalCompileWarnings"} configuration "Debug" optimize "Debug" - +buildoptions {"/bigobj"} defines {"DEBUG", "_DEBUG"} configuration {} From bd3cdbb23d48bdfd41d1178583ad052e7c0e588f Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Mon, 13 Sep 2021 04:08:11 +0200 Subject: [PATCH 07/11] Some fixes --- src/client/game/ui_scripting/lua/context.cpp | 1 + src/client/game/ui_scripting/lua/engine.cpp | 4 +- src/client/resources/animation.lua | 115 ++++++++++--------- 3 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 6ee57f9a..77d8971f 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -757,6 +757,7 @@ namespace ui_scripting::lua { this->state_.collect_garbage(); this->scheduler_.clear(); + this->event_handler_.clear(); this->state_ = {}; } diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index cdc60b94..eee59987 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -358,15 +358,15 @@ namespace ui_scripting::lua::engine void start() { - clear_menus(); get_scripts().clear(); + clear_menus(); load_scripts(); } void stop() { - clear_menus(); get_scripts().clear(); + clear_menus(); } void ui_event(const std::string& type, const std::vector& arguments) diff --git a/src/client/resources/animation.lua b/src/client/resources/animation.lua index 3bff9bea..0294c264 100644 --- a/src/client/resources/animation.lua +++ b/src/client/resources/animation.lua @@ -1,86 +1,91 @@ -function element:animate(state, animationtime) - self:notify("cancel_animation") +function element:animate(name, state, animationtime) + local start = { + x = self.x, + y = self.y, + w = self.w, + h = self.h, + color = self.color, + backcolor = self.backcolor, + bordercolor = self.bordercolor, + borderwidth = self.borderwidth, + fontsize = self.fontsize + } - local doanimation = function() - local start = { - x = self.x, - y = self.y, - w = self.w, - h = self.h, - color = self.color, - backcolor = self.backcolor, - bordercolor = self.bordercolor, - borderwidth = self.borderwidth, - fontsize = self.fontsize - } - - local _end = {} - for k, v in pairs(start) do - _end[k] = state[k] or v - end + local _end = {} + for k, v in pairs(start) do + _end[k] = state[k] or v + end - local diffs = {} - for k, v in pairs(_end) do - if (type(v) == "table") then - local value = {} + local diffs = {} + for k, v in pairs(_end) do + if (type(v) == "table") then + local value = {} + local different = false - for _k, _v in pairs(v) do - value[_k] = _v - start[k][_k] + for _k, _v in pairs(v) do + value[_k] = _v - start[k][_k] + if (value[_k] ~= 0) then + different = true end + end + if (different) then diffs[k] = value - else + end + else + local value = v - start[k] + if (value ~= 0) then diffs[k] = v - start[k] end end - - local timeout = nil - local interval = nil - local starttime = game:time() + end - interval = game:onframe(function() - local time = game:time() - local percentage = (time - starttime) / animationtime - if (percentage >= 1) then - for k, v in pairs(_end) do - self[k] = v - end - return + local timeout = nil + local interval = nil + local starttime = game:time() + + interval = game:onframe(function() + local time = game:time() + local percentage = (time - starttime) / animationtime + + if (percentage >= 1) then + for k, v in pairs(diffs) do + self[k] = _end[k] end - + else for k, v in pairs(diffs) do if (type(v) == "table") then local value = {} - + for _k, _v in pairs(v) do value[_k] = start[k][_k] + _v * percentage end - + self[k] = value else self[k] = start[k] + v * percentage end end - end) + end + end) - timeout = game:ontimeout(function() - interval:clear() - for k, v in pairs(_end) do - self[k] = v - end - end, animationtime) + timeout = game:ontimeout(function() + interval:clear() + for k, v in pairs(diffs) do + self[k] = _end[k] + end + end, animationtime) - self:onnotifyonce("cancel_animation", function() + self:onnotifyonce("cancel_animation", function(_name) + if (name == _name) then timeout:clear() interval:clear() - end) - end - - game:ontimeout(doanimation, 0) + end + end) end -function element:cancelanimations(callback) - self:notify("cancel_animation") +function element:cancelanimations(name, callback) + self:notify("cancel_animation", name) if (type(callback) == "function") then game:ontimeout(callback, 0) end From 192df6edc40ec3a6882496f116eb10f286bb66cf Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Mon, 13 Sep 2021 22:34:41 +0200 Subject: [PATCH 08/11] Some more fixes --- src/client/game/ui_scripting/element.cpp | 101 +++++++++++--------- src/client/game/ui_scripting/lua/engine.cpp | 32 ++++--- 2 files changed, 75 insertions(+), 58 deletions(-) diff --git a/src/client/game/ui_scripting/element.cpp b/src/client/game/ui_scripting/element.cpp index 826d9281..68e11d7c 100644 --- a/src/client/game/ui_scripting/element.cpp +++ b/src/client/game/ui_scripting/element.cpp @@ -262,58 +262,65 @@ namespace ui_scripting { check_resize(); - const auto background_material = game::Material_RegisterHandle(this->material.data()); - draw_image( - relative(this->x) + relative(this->border_width[3]), - relative(this->y) + relative(this->border_width[0]), - relative(this->w), - relative(this->h), - (float*)this->slice, - (float*)this->background_color, - background_material - ); + if (this->background_color[3] > 0) + { + const auto background_material = game::Material_RegisterHandle(this->material.data()); - const auto _border_material = game::Material_RegisterHandle(this->border_material.data()); + draw_image( + relative(this->x) + relative(this->border_width[3]), + relative(this->y) + relative(this->border_width[0]), + relative(this->w), + relative(this->h), + (float*)this->slice, + (float*)this->background_color, + background_material + ); + } - draw_image( - relative(this->x), - relative(this->y), - relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]), - relative(this->border_width[0]), - (float*)this->slice, - (float*)this->border_color, - _border_material - ); + if (this->border_color[3] > 0) + { + const auto _border_material = game::Material_RegisterHandle(this->border_material.data()); - draw_image( - relative(this->x) + relative(this->border_width[3]) + relative(this->w), - relative(this->y) + relative(this->border_width[0]), - relative(this->border_width[1]), - relative(this->h), - (float*)this->slice, - (float*)this->border_color, - _border_material - ); + draw_image( + relative(this->x), + relative(this->y), + relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]), + relative(this->border_width[0]), + (float*)this->slice, + (float*)this->border_color, + _border_material + ); - draw_image( - relative(this->x), - relative(this->y) + relative(this->h) + relative(this->border_width[0]), - relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]), - relative(this->border_width[2]), - (float*)this->slice, - (float*)this->border_color, - _border_material - ); + draw_image( + relative(this->x) + relative(this->border_width[3]) + relative(this->w), + relative(this->y) + relative(this->border_width[0]), + relative(this->border_width[1]), + relative(this->h), + (float*)this->slice, + (float*)this->border_color, + _border_material + ); - draw_image( - relative(this->x), - relative(this->y) + relative(this->border_width[0]), - relative(this->border_width[3]), - relative(this->h), - (float*)this->slice, - (float*)this->border_color, - _border_material - ); + draw_image( + relative(this->x), + relative(this->y) + relative(this->h) + relative(this->border_width[0]), + relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]), + relative(this->border_width[2]), + (float*)this->slice, + (float*)this->border_color, + _border_material + ); + + draw_image( + relative(this->x), + relative(this->y) + relative(this->border_width[0]), + relative(this->border_width[3]), + relative(this->h), + (float*)this->slice, + (float*)this->border_color, + _border_material + ); + } if (!this->text.empty()) { diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index eee59987..6f7a882a 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -2,6 +2,8 @@ #include "engine.hpp" #include "context.hpp" +#include "../../../component/scheduler.hpp" + #include #include @@ -216,6 +218,11 @@ namespace ui_scripting::lua::engine std::vector previous_elements; void handle_mousemove_event(const int x, const int y) { + if (mouse[0] == x && mouse[1] == y) + { + return; + } + mouse[0] = x; mouse[1] = y; @@ -371,20 +378,23 @@ namespace ui_scripting::lua::engine void ui_event(const std::string& type, const std::vector& arguments) { - if (type == "key") + ::scheduler::once([type, arguments]() { - handle_key_event(arguments[0], arguments[1]); - } + if (type == "key") + { + handle_key_event(arguments[0], arguments[1]); + } - if (type == "char") - { - handle_char_event(arguments[0]); - } + if (type == "char") + { + handle_char_event(arguments[0]); + } - if (type == "mousemove") - { - handle_mousemove_event(relative_mouse(arguments[0]), relative_mouse(arguments[1])); - } + if (type == "mousemove") + { + handle_mousemove_event(relative_mouse(arguments[0]), relative_mouse(arguments[1])); + } + }, ::scheduler::pipeline::renderer); } void notify(const event& e) From ccaefd866cffcd02b495a351053eccb4e16ee52c Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 14 Sep 2021 02:48:11 +0200 Subject: [PATCH 09/11] More fixes --- src/client/component/ui_scripting.cpp | 1 - src/client/game/ui_scripting/lua/context.cpp | 132 +++++++++++++++--- src/client/game/ui_scripting/lua/engine.cpp | 18 ++- src/client/game/ui_scripting/lua/event.hpp | 2 +- .../game/ui_scripting/lua/event_handler.cpp | 6 +- .../game/ui_scripting/lua/event_handler.hpp | 2 +- .../game/ui_scripting/lua/scheduler.cpp | 2 +- src/client/game/ui_scripting/menu.cpp | 28 +--- src/client/game/ui_scripting/menu.hpp | 1 - 9 files changed, 138 insertions(+), 54 deletions(-) diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 06ad4421..362b5501 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -28,7 +28,6 @@ namespace ui_scripting scheduler::loop([]() { - ShowCursor(true); ui_scripting::lua::engine::run_frame(); }, scheduler::pipeline::renderer); diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index 77d8971f..bc121d2d 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -206,18 +206,6 @@ namespace ui_scripting::lua element_type["settextoffset"] = &element::set_text_offset; element_type["setslice"] = &element::set_slice; - element_type["onnotify"] = [&handler](element& element, const std::string& event, - const event_callback& callback) - { - event_listener listener{}; - listener.callback = callback; - listener.element = &element; - listener.event = event; - listener.is_volatile = false; - - return handler.add_event_listener(std::move(listener)); - }; - element_type["getrect"] = [](const sol::this_state s, element& element) { auto rect = sol::table::create(s.lua_state()); @@ -371,6 +359,18 @@ namespace ui_scripting::lua } ); + element_type["onnotify"] = [&handler](element& element, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &element; + listener.event = event; + listener.is_volatile = false; + + return handler.add_event_listener(std::move(listener)); + }; + element_type["onnotifyonce"] = [&handler](element& element, const std::string& event, const event_callback& callback) { @@ -408,6 +408,53 @@ namespace ui_scripting::lua auto menu_type = state.new_usertype("menu"); + menu_type["onnotify"] = [&handler](menu& menu, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &menu; + listener.event = event; + listener.is_volatile = false; + + return handler.add_event_listener(std::move(listener)); + }; + + menu_type["onnotifyonce"] = [&handler](menu& menu, const std::string& event, + const event_callback& callback) + { + event_listener listener{}; + listener.callback = callback; + listener.element = &menu; + listener.event = event; + listener.is_volatile = true; + + return handler.add_event_listener(std::move(listener)); + }; + + menu_type["notify"] = [&handler](menu& element, const sol::this_state s, const std::string& _event, + sol::variadic_args va) + { + event event; + event.element = &element; + event.name = _event; + + for (auto arg : va) + { + if (arg.get_type() == sol::type::number) + { + event.arguments.push_back(arg.as()); + } + + if (arg.get_type() == sol::type::string) + { + event.arguments.push_back(arg.as()); + } + } + + handler.dispatch(event); + }; + menu_type["addchild"] = [](const sol::this_state s, menu& menu, element& element) { menu.add_child(&element); @@ -424,6 +471,31 @@ namespace ui_scripting::lua } ); + menu_type["isopen"] = [](menu& menu) + { + return menu.visible || (menu.type == menu_type::overlay && game::Menu_IsMenuOpenAndVisible(0, menu.overlay_menu.data())); + }; + + menu_type["open"] = [&handler](menu& menu) + { + event event; + event.element = &menu; + event.name = "close"; + handler.dispatch(event); + + menu.open(); + }; + + menu_type["close"] = [&handler](menu& menu) + { + event event; + event.element = &menu; + event.name = "close"; + handler.dispatch(event); + + menu.close(); + }; + struct game { }; @@ -459,24 +531,38 @@ namespace ui_scripting::lua return pos; }; - game_type["openmenu"] = [](const game&, const std::string& name) + game_type["openmenu"] = [&handler](const game&, const std::string& name) { if (menus.find(name) == menus.end()) { return; } - menus[name].open(); + const auto menu = &menus[name]; + + event event; + event.element = menu; + event.name = "close"; + handler.dispatch(event); + + menu->open(); }; - game_type["closemenu"] = [](const game&, const std::string& name) + game_type["closemenu"] = [&handler](const game&, const std::string& name) { if (menus.find(name) == menus.end()) { return; } - menus[name].close(); + const auto menu = &menus[name]; + + event event; + event.element = menu; + event.name = "close"; + handler.dispatch(event); + + menu->close(); }; game_type["onframe"] = [&scheduler](const game&, const sol::protected_function& callback) @@ -646,15 +732,15 @@ namespace ui_scripting::lua throw std::runtime_error("Not in game"); } - std::vector arguments{}; - - for (auto arg : va) + ::scheduler::once([s, name, args = std::vector(va.begin(), va.end())]() { - arguments.push_back(convert({s, arg})); - } + std::vector arguments{}; + + for (auto arg : args) + { + arguments.push_back(convert({s, arg})); + } - ::scheduler::once([s, va, name, arguments]() - { const auto player = scripting::call("getentbynum", {0}).as(); scripting::notify(player, name, arguments); }, ::scheduler::pipeline::server); diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index 6f7a882a..fe16b457 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -350,7 +350,14 @@ namespace ui_scripting::lua::engine return; } - menus[name].open(); + const auto menu = &menus[name]; + + event event; + event.element = menu; + event.name = "open"; + notify(event); + + menu->open(); } void close_menu(const std::string& name) @@ -360,7 +367,14 @@ namespace ui_scripting::lua::engine return; } - menus[name].close(); + const auto menu = &menus[name]; + + event event; + event.element = menu; + event.name = "close"; + notify(event); + + menu->close(); } void start() diff --git a/src/client/game/ui_scripting/lua/event.hpp b/src/client/game/ui_scripting/lua/event.hpp index 428a1eda..54f79b3a 100644 --- a/src/client/game/ui_scripting/lua/event.hpp +++ b/src/client/game/ui_scripting/lua/event.hpp @@ -7,7 +7,7 @@ namespace ui_scripting::lua struct event { std::string name; - element* element{}; + const void* element{}; std::vector> arguments; }; } diff --git a/src/client/game/ui_scripting/lua/event_handler.cpp b/src/client/game/ui_scripting/lua/event_handler.cpp index 73fbf336..727a92dd 100644 --- a/src/client/game/ui_scripting/lua/event_handler.cpp +++ b/src/client/game/ui_scripting/lua/event_handler.cpp @@ -35,7 +35,7 @@ namespace ui_scripting::lua for (auto i = tasks.begin(); i != tasks.end();) { - if (i->event != event.name || i->element->id != event.element->id) + if (i->event != event.name || i->element != event.element) { ++i; continue; @@ -87,7 +87,7 @@ namespace ui_scripting::lua { if (task.id == handle.id) { - task.endon_conditions.emplace_back(element->id, event); + task.endon_conditions.emplace_back((uint64_t)element, event); } } }; @@ -150,7 +150,7 @@ namespace ui_scripting::lua { for (auto& condition : task.endon_conditions) { - if (condition.first == event.element->id && condition.second == event.name) + if (condition.first == (uint64_t)event.element && condition.second == event.name) { task.is_deleted = true; break; diff --git a/src/client/game/ui_scripting/lua/event_handler.hpp b/src/client/game/ui_scripting/lua/event_handler.hpp index 88eb657a..a203a23a 100644 --- a/src/client/game/ui_scripting/lua/event_handler.hpp +++ b/src/client/game/ui_scripting/lua/event_handler.hpp @@ -16,7 +16,7 @@ namespace ui_scripting::lua { public: std::string event = {}; - element* element{}; + void* element{}; event_callback callback = {}; bool is_volatile = false; bool is_deleted = false; diff --git a/src/client/game/ui_scripting/lua/scheduler.cpp b/src/client/game/ui_scripting/lua/scheduler.cpp index 973adcec..8ecb930a 100644 --- a/src/client/game/ui_scripting/lua/scheduler.cpp +++ b/src/client/game/ui_scripting/lua/scheduler.cpp @@ -27,7 +27,7 @@ namespace ui_scripting::lua { for (auto& condition : task.endon_conditions) { - if (condition.first == event.element->id && condition.second == event.name) + if (condition.first == (uint64_t)event.element && condition.second == event.name) { task.is_deleted = true; break; diff --git a/src/client/game/ui_scripting/menu.cpp b/src/client/game/ui_scripting/menu.cpp index c535b49f..844c90ef 100644 --- a/src/client/game/ui_scripting/menu.cpp +++ b/src/client/game/ui_scripting/menu.cpp @@ -16,37 +16,23 @@ namespace ui_scripting void menu::open() { - if (this->visible) - { - return; - } - - this->cursor_was_enabled = *game::keyCatchers & 0x40; - if (!this->cursor_was_enabled && this->cursor) - { - *game::keyCatchers |= 0x40; - } - + *game::keyCatchers |= 0x40; this->visible = true; } void menu::close() { - if (!this->visible) - { - return; - } - - if (!this->cursor_was_enabled && this->cursor) - { - *game::keyCatchers &= ~0x40; - } - + *game::keyCatchers &= ~0x40; this->visible = false; } void menu::render() const { + if (this->cursor) + { + *game::keyCatchers |= 0x40; + } + for (auto& element : this->children) { element->render(); diff --git a/src/client/game/ui_scripting/menu.hpp b/src/client/game/ui_scripting/menu.hpp index 70121050..a726fdaa 100644 --- a/src/client/game/ui_scripting/menu.hpp +++ b/src/client/game/ui_scripting/menu.hpp @@ -17,7 +17,6 @@ namespace ui_scripting bool visible = false; bool cursor = false; - bool cursor_was_enabled = false; void open(); void close(); From d901845e03f9306acb32abd2e506ef0f364e4482 Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 14 Sep 2021 02:59:36 +0200 Subject: [PATCH 10/11] Close menus --- src/client/game/ui_scripting/lua/engine.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/client/game/ui_scripting/lua/engine.cpp b/src/client/game/ui_scripting/lua/engine.cpp index fe16b457..563b43ef 100644 --- a/src/client/game/ui_scripting/lua/engine.cpp +++ b/src/client/game/ui_scripting/lua/engine.cpp @@ -331,9 +331,28 @@ namespace ui_scripting::lua::engine } } + void close_all_menus() + { + for (auto& menu : menus) + { + if (!is_menu_visible(menu.second)) + { + continue; + } + + event event; + event.element = &menu.second; + event.name = "close"; + notify(event); + + menu.second.close(); + } + } + void clear_menus() { menus.clear(); + for (const auto element : elements) { delete element; @@ -379,6 +398,7 @@ namespace ui_scripting::lua::engine void start() { + close_all_menus(); get_scripts().clear(); clear_menus(); load_scripts(); @@ -386,6 +406,7 @@ namespace ui_scripting::lua::engine void stop() { + close_all_menus(); get_scripts().clear(); clear_menus(); } From a679544531dd20fbe938191d3ff70826549bf84e Mon Sep 17 00:00:00 2001 From: Federico Cecchetto Date: Tue, 14 Sep 2021 03:29:30 +0200 Subject: [PATCH 11/11] Last fix --- src/client/game/ui_scripting/lua/context.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index bc121d2d..58f513e8 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -741,7 +741,14 @@ namespace ui_scripting::lua arguments.push_back(convert({s, arg})); } - const auto player = scripting::call("getentbynum", {0}).as(); + const auto player_value = scripting::call("getentbynum", {0}); + if (player_value.get_raw().type != ::game::SCRIPT_OBJECT) + { + return; + } + + const auto player = player_value.as(); + scripting::notify(player, name, arguments); }, ::scheduler::pipeline::server); };