UI scripting test
This commit is contained in:
parent
43b875924a
commit
4de5509092
@ -4,6 +4,7 @@
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game_console.hpp"
|
||||
#include "game/ui_scripting/lua/engine.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
@ -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<void>(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<void>(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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
77
src/client/component/ui_scripting.cpp
Normal file
77
src/client/component/ui_scripting.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
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)
|
6
src/client/component/ui_scripting.hpp
Normal file
6
src/client/component/ui_scripting.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "game/ui_scripting/menu.hpp"
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
}
|
@ -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)
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -43,6 +43,7 @@ namespace game
|
||||
unsigned int flags)> Dvar_RegisterVec4{0x6185F0};
|
||||
WEAK symbol<const char* (dvar_t* dvar, void* a2, void* value)> Dvar_ValueToString{0x61B8F0};
|
||||
WEAK symbol<void(int hash, const char* name, const char* buffer)> Dvar_SetCommand{0x61A5C0};
|
||||
WEAK symbol<void(const char* dvarName, const char* string, DvarSetSource source)> Dvar_SetFromStringFromSource{0x61A910};
|
||||
|
||||
WEAK symbol<int(const char* fname)> generateHashValue{0x343D20};
|
||||
|
||||
@ -78,6 +79,8 @@ namespace game
|
||||
|
||||
WEAK symbol<void(float x, float y, float width, float height, float s0, float t0, float s1, float t1,
|
||||
float* color, Material* material)> R_AddCmdDrawStretchPic{0x3C9710};
|
||||
WEAK symbol<void(float x, float y, float width, float height, float s0, float t0, float s1, float t1,
|
||||
float angle, float* color, Material* material)> R_AddCmdDrawStretchPicRotateXY{0x3C99B0};
|
||||
WEAK symbol<void(const char* text, int maxChars, Font_s* font, float x, float y, float xScale, float yScale,
|
||||
float rotation, float* color, int style)> R_AddCmdDrawText{0x76C660};
|
||||
WEAK symbol<void(const char* text, int maxChars, Font_s* font, int fontSize, float x, float y, float xScale, float yScale, float rotation,
|
||||
|
250
src/client/game/ui_scripting/element.cpp
Normal file
250
src/client/game/ui_scripting/element.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
#include <std_include.hpp>
|
||||
#include "element.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#define fps_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 25)
|
||||
|
||||
namespace ui_scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
uint64_t next_id;
|
||||
|
||||
std::unordered_map<std::string, std::string> 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<std::string, alignment> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
63
src/client/game/ui_scripting/element.hpp
Normal file
63
src/client/game/ui_scripting/element.hpp
Normal file
@ -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{};
|
||||
};
|
||||
}
|
891
src/client/game/ui_scripting/lua/context.cpp
Normal file
891
src/client/game/ui_scripting/lua/context.cpp
Normal file
@ -0,0 +1,891 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <utils/string.hpp>
|
||||
|
||||
namespace ui_scripting::lua
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, menu> menus;
|
||||
std::vector<element*> elements;
|
||||
element ui_element;
|
||||
|
||||
scripting::script_value convert(const sol::lua_value& value)
|
||||
{
|
||||
if (value.is<int>())
|
||||
{
|
||||
return {value.as<int>()};
|
||||
}
|
||||
|
||||
if (value.is<unsigned int>())
|
||||
{
|
||||
return {value.as<unsigned int>()};
|
||||
}
|
||||
|
||||
if (value.is<bool>())
|
||||
{
|
||||
return {value.as<bool>()};
|
||||
}
|
||||
|
||||
if (value.is<double>())
|
||||
{
|
||||
return {value.as<double>()};
|
||||
}
|
||||
|
||||
if (value.is<float>())
|
||||
{
|
||||
return {value.as<float>()};
|
||||
}
|
||||
if (value.is<std::string>())
|
||||
{
|
||||
return {value.as<std::string>()};
|
||||
}
|
||||
|
||||
if (value.is<scripting::vector>())
|
||||
{
|
||||
return {value.as<scripting::vector>()};
|
||||
}
|
||||
|
||||
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<element*> elements_in_point(int x, int y)
|
||||
{
|
||||
std::vector<element*> 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<element*> 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>("scripting::vector", sol::constructors<scripting::vector(float, float, float)>());
|
||||
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>("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<void(element::*)(float)>(&element::set_border_width),
|
||||
static_cast<void(element::*)(float, float)>(&element::set_border_width),
|
||||
static_cast<void(element::*)(float, float, float)>(&element::set_border_width),
|
||||
static_cast<void(element::*)(float, float, float, float)>(&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");
|
||||
|
||||
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>("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<bool>())
|
||||
{
|
||||
string_value = utils::string::va("%i", value.as<bool>());
|
||||
}
|
||||
else if (value.is<int>())
|
||||
{
|
||||
string_value = utils::string::va("%i", value.as<int>());
|
||||
}
|
||||
else if (value.is<float>())
|
||||
{
|
||||
string_value = utils::string::va("%f", value.as<float>());
|
||||
}
|
||||
else if (value.is<scripting::vector>())
|
||||
{
|
||||
const auto v = value.as<scripting::vector>();
|
||||
string_value = utils::string::va("%f %f %f",
|
||||
v.get_x(),
|
||||
v.get_y(),
|
||||
v.get_z()
|
||||
);
|
||||
}
|
||||
|
||||
if (value.is<std::string>())
|
||||
{
|
||||
string_value = value.as<std::string>();
|
||||
}
|
||||
|
||||
::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<std::vector<float>>();
|
||||
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>("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<scripting::script_value> 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::entity>();
|
||||
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<int>& 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));
|
||||
}
|
||||
}
|
45
src/client/game/ui_scripting/lua/context.hpp
Normal file
45
src/client/game/ui_scripting/lua/context.hpp
Normal file
@ -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 <sol/sol.hpp>
|
||||
|
||||
#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<int>&);
|
||||
|
||||
private:
|
||||
sol::state state_{};
|
||||
std::string folder_;
|
||||
std::unordered_set<std::string> loaded_scripts_;
|
||||
|
||||
scheduler scheduler_;
|
||||
event_handler event_handler_;
|
||||
|
||||
void load_script(const std::string& script);
|
||||
};
|
||||
}
|
67
src/client/game/ui_scripting/lua/engine.cpp
Normal file
67
src/client/game/ui_scripting/lua/engine.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include <std_include.hpp>
|
||||
#include "engine.hpp"
|
||||
#include "context.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace ui_scripting::lua::engine
|
||||
{
|
||||
namespace
|
||||
{
|
||||
auto& get_scripts()
|
||||
{
|
||||
static std::vector<std::unique_ptr<context>> 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<context>(script));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
get_scripts().clear();
|
||||
load_scripts();
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
get_scripts().clear();
|
||||
}
|
||||
|
||||
void ui_event(const std::string& type, const std::vector<int>& arguments)
|
||||
{
|
||||
for (auto& script : get_scripts())
|
||||
{
|
||||
script->ui_event(type, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
void run_frame()
|
||||
{
|
||||
for (auto& script : get_scripts())
|
||||
{
|
||||
script->run_frame();
|
||||
}
|
||||
}
|
||||
}
|
10
src/client/game/ui_scripting/lua/engine.hpp
Normal file
10
src/client/game/ui_scripting/lua/engine.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace ui_scripting::lua::engine
|
||||
{
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void ui_event(const std::string&, const std::vector<int>&);
|
||||
void run_frame();
|
||||
}
|
24
src/client/game/ui_scripting/lua/error.cpp
Normal file
24
src/client/game/ui_scripting/lua/error.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include <std_include.hpp>
|
||||
#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 (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
src/client/game/ui_scripting/lua/error.hpp
Normal file
8
src/client/game/ui_scripting/lua/error.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace ui_scripting::lua
|
||||
{
|
||||
void handle_error(const sol::protected_function_result& result);
|
||||
}
|
13
src/client/game/ui_scripting/lua/event.hpp
Normal file
13
src/client/game/ui_scripting/lua/event.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace ui_scripting::lua
|
||||
{
|
||||
struct event
|
||||
{
|
||||
std::string name;
|
||||
element* element{};
|
||||
std::vector<sol::lua_value> arguments;
|
||||
};
|
||||
}
|
107
src/client/game/ui_scripting/lua/event_handler.cpp
Normal file
107
src/client/game/ui_scripting/lua/event_handler.cpp
Normal file
@ -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");
|
||||
|
||||
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<task_list::iterator>(new_tasks.begin()),
|
||||
std::move_iterator<task_list::iterator>(new_tasks.end()));
|
||||
new_tasks = {};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
54
src/client/game/ui_scripting/lua/event_handler.hpp
Normal file
54
src/client/game/ui_scripting/lua/event_handler.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <utils/concurrency.hpp>
|
||||
#include "../element.hpp"
|
||||
|
||||
namespace ui_scripting::lua
|
||||
{
|
||||
using event_arguments = std::vector<sol::lua_value>;
|
||||
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<event_listener>;
|
||||
utils::concurrency::container<task_list> new_callbacks_;
|
||||
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
|
||||
|
||||
void remove(const event_listener_handle& handle);
|
||||
void merge_callbacks();
|
||||
};
|
||||
}
|
122
src/client/game/ui_scripting/lua/scheduler.cpp
Normal file
122
src/client/game/ui_scripting/lua/scheduler.cpp
Normal file
@ -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");
|
||||
|
||||
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<task_list::iterator>(new_tasks.begin()),
|
||||
std::move_iterator<task_list::iterator>(new_tasks.end()));
|
||||
new_tasks = {};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
50
src/client/game/ui_scripting/lua/scheduler.hpp
Normal file
50
src/client/game/ui_scripting/lua/scheduler.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
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<task>;
|
||||
utils::concurrency::container<task_list> new_callbacks_;
|
||||
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
|
||||
std::atomic_int64_t current_task_id_ = 0;
|
||||
|
||||
void remove(const task_handle& handle);
|
||||
void merge_callbacks();
|
||||
};
|
||||
}
|
55
src/client/game/ui_scripting/menu.cpp
Normal file
55
src/client/game/ui_scripting/menu.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include <std_include.hpp>
|
||||
#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();
|
||||
}
|
||||
}
|
||||
}
|
24
src/client/game/ui_scripting/menu.hpp
Normal file
24
src/client/game/ui_scripting/menu.hpp
Normal file
@ -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<element*> children{};
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user