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{}; + }; +}