Merge pull request #22 from fedddddd/ui_scripting

UI scripting
This commit is contained in:
fed 2021-09-14 03:59:24 +02:00 committed by GitHub
commit c068308176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2603 additions and 8 deletions

View File

@ -263,7 +263,7 @@ flags {"FatalCompileWarnings"}
configuration "Debug"
optimize "Debug"
buildoptions {"/bigobj"}
defines {"DEBUG", "_DEBUG"}
configuration {}

View File

@ -34,7 +34,7 @@ namespace images
return {};
}
return { std::move(data) };
return {std::move(data)};
}
std::optional<utils::image> load_raw_image_from_file(game::GfxImage* image)

View File

@ -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);
}
};
}

View File

@ -0,0 +1,63 @@
#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 <utils/string.hpp>
namespace ui_scripting
{
class component final : public component_interface
{
public:
void post_unpack() override
{
scheduler::once([]()
{
ui_scripting::lua::engine::start();
}, scheduler::pipeline::renderer);
scheduler::loop([]()
{
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)
{
const std::string name = params.get(1);
scheduler::once([name]()
{
ui_scripting::lua::engine::open_menu(name);
}, scheduler::pipeline::renderer);
});
command::add("closeluamenu", [](const command::params& params)
{
const std::string name = params.get(1);
scheduler::once([name]()
{
ui_scripting::lua::engine::close_menu(name);
}, scheduler::pipeline::renderer);
});
}
};
}
REGISTER_COMPONENT(ui_scripting::component)

View File

@ -0,0 +1,6 @@
#pragma once
#include "game/ui_scripting/menu.hpp"
namespace ui_scripting
{
}

View File

@ -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)

View File

@ -72,6 +72,22 @@ namespace game
const char* name;
};
struct point
{
float x;
float y;
float f2;
float f3;
};
struct rectangle
{
point p0;
point p1;
point p2;
point p3;
};
struct Glyph
{
unsigned short letter;

View File

@ -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,8 +79,11 @@ 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(rectangle* rect, float a2, float a3, float a4, float a5, float* color, Material* material)> R_DrawRectangle{0x76A280};
WEAK symbol<void(const char* text, int maxChars, Font_s* font, int fontSize, float x, float y, float xScale, float yScale, float rotation,
const float* color, int style, int cursorPos, char cursor)> R_AddCmdDrawTextWithCursor{0x76CAF0};
WEAK symbol<Font_s*(const char* font, int size)> R_RegisterFont{0x746FE0};
@ -99,6 +103,7 @@ namespace game
WEAK symbol<bool()> Sys_IsDatabaseReady2{0x5A9FE0};
WEAK symbol<const char*(const char* string)> UI_SafeTranslateString{0x5A2930};
WEAK symbol<int(int localClientNum, const char* sound)> UI_PlayLocalSoundAlias{0x606080};
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x89EED0};
WEAK symbol<int(jmp_buf* Buf)> _setjmp{0x8EC2E0};

View File

@ -0,0 +1,342 @@
#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;
float screen_max[2];
struct point
{
float x;
float y;
float f2;
float f3;
};
struct rectangle
{
point p0;
point p1;
point p2;
point p3;
};
std::unordered_map<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;
}
}
void draw_image(float x, float y, float w, float h, float* transform, float* color, game::Material* material)
{
game::rectangle rect;
rect.p0.x = x;
rect.p0.y = y;
rect.p0.f2 = 0.f;
rect.p0.f3 = 1.f;
rect.p1.x = x + w;
rect.p1.y = y;
rect.p1.f2 = 0.f;
rect.p1.f3 = 1.f;
rect.p2.x = x + w;
rect.p2.y = y + h;
rect.p2.f2 = 0.f;
rect.p2.f3 = 1.f;
rect.p3.x = x;
rect.p3.y = y + h;
rect.p3.f2 = 0.f;
rect.p3.f3 = 1.f;
game::R_DrawRectangle(&rect, transform[0], transform[1], transform[2], transform[3], color, material);
}
void check_resize()
{
screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0];
screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1];
}
float relative(float value)
{
return (value / 1920.f) * screen_max[0];
}
int relative(int value)
{
return (int)(((float)value / 1920.f) * screen_max[0]);
}
}
element::element()
: 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 = "default";
}
else
{
this->font = lowercase;
}
}
void element::set_font(const std::string& _font)
{
const auto lowercase = utils::string::to_lower(_font);
if (font_map.find(lowercase) == font_map.end())
{
this->font = "default";
}
else
{
this->font = lowercase;
}
}
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_slice(float left_percent, float top_percent, float right_percent, float bottom_percent)
{
this->slice[0] = left_percent;
this->slice[1] = top_percent;
this->slice[2] = right_percent;
this->slice[3] = bottom_percent;
}
void element::set_material(const std::string& _material)
{
this->material = _material;
}
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
{
check_resize();
if (this->background_color[3] > 0)
{
const auto background_material = game::Material_RegisterHandle(this->material.data());
draw_image(
relative(this->x) + relative(this->border_width[3]),
relative(this->y) + relative(this->border_width[0]),
relative(this->w),
relative(this->h),
(float*)this->slice,
(float*)this->background_color,
background_material
);
}
if (this->border_color[3] > 0)
{
const auto _border_material = game::Material_RegisterHandle(this->border_material.data());
draw_image(
relative(this->x),
relative(this->y),
relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]),
relative(this->border_width[0]),
(float*)this->slice,
(float*)this->border_color,
_border_material
);
draw_image(
relative(this->x) + relative(this->border_width[3]) + relative(this->w),
relative(this->y) + relative(this->border_width[0]),
relative(this->border_width[1]),
relative(this->h),
(float*)this->slice,
(float*)this->border_color,
_border_material
);
draw_image(
relative(this->x),
relative(this->y) + relative(this->h) + relative(this->border_width[0]),
relative(this->w) + relative(this->border_width[1]) + relative(this->border_width[3]),
relative(this->border_width[2]),
(float*)this->slice,
(float*)this->border_color,
_border_material
);
draw_image(
relative(this->x),
relative(this->y) + relative(this->border_width[0]),
relative(this->border_width[3]),
relative(this->h),
(float*)this->slice,
(float*)this->border_color,
_border_material
);
}
if (!this->text.empty())
{
const auto fontname = font_map[this->font];
const auto _font = game::R_RegisterFont(fontname.data(), relative(this->fontsize));
const auto text_width = game::R_TextWidth(this->text.data(), 0x7FFFFFFF, _font);
auto _horzalign = get_align_value(this->horzalign, (float)text_width, relative(this->w));
auto _vertalign = get_align_value(this->vertalign, (float)relative(this->fontsize), relative(this->h));
game::R_AddCmdDrawText(this->text.data(), 0x7FFFFFFF, _font,
relative(this->x) + relative(this->text_offset[0]) + _horzalign + relative(this->border_width[3]),
relative(this->y) + relative(this->text_offset[1]) + _vertalign + relative(this->fontsize) + relative(this->border_width[0]),
1.0f, 1.0f, 0.0f,
(float*)this->color, 0
);
}
}
}

View File

@ -0,0 +1,67 @@
#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);
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_slice(float left_percent, float top_percent, float right_percent, float bottom_percent);
void set_rect(const float _x, const float _y, const float _w, const float _h);
uint64_t id;
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};
float slice[4] = {0.f, 0.f, 1.f, 1.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{};
};
}

View File

@ -0,0 +1,879 @@
#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>
#include <utils/nt.hpp>
namespace ui_scripting::lua
{
std::unordered_map<std::string, menu> menus;
std::vector<element*> elements;
element ui_element;
int mouse[2];
namespace
{
const auto animation_script = utils::nt::load_resource(LUA_ANIMATION_SCRIPT);
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 valid_dvar_name(const std::string& name)
{
for (const auto c : name)
{
if (!isalnum(c))
{
return false;
}
}
return true;
}
void setup_types(sol::state& state, event_handler& handler, scheduler& scheduler)
{
auto vector_type = state.new_usertype<scripting::vector>("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"] = sol::overload(
static_cast<void(element::*)(const std::string&)>(&element::set_font),
static_cast<void(element::*)(const std::string&, int)>(&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["setslice"] = &element::set_slice;
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["x"] = sol::property(
[](element& element)
{
return element.x;
},
[](element& element, float x)
{
element.x = x;
}
);
element_type["y"] = sol::property(
[](element& element)
{
return element.y;
},
[](element& element, float y)
{
element.y = y;
}
);
element_type["w"] = sol::property(
[](element& element)
{
return element.w;
},
[](element& element, float w)
{
element.w = w;
}
);
element_type["h"] = sol::property(
[](element& element)
{
return element.h;
},
[](element& element, float h)
{
element.h = h;
}
);
element_type["color"] = sol::property(
[](element& element, const sol::this_state s)
{
auto color = sol::table::create(s.lua_state());
color["r"] = element.color[0];
color["g"] = element.color[1];
color["b"] = element.color[2];
color["a"] = element.color[3];
return color;
},
[](element& element, const sol::lua_table color)
{
element.color[0] = color["r"].get_type() == sol::type::number ? color["r"].get<float>() : 0.f;
element.color[1] = color["g"].get_type() == sol::type::number ? color["g"].get<float>() : 0.f;
element.color[2] = color["b"].get_type() == sol::type::number ? color["b"].get<float>() : 0.f;
element.color[3] = color["a"].get_type() == sol::type::number ? color["a"].get<float>() : 0.f;
}
);
element_type["backcolor"] = sol::property(
[](element& element, const sol::this_state s)
{
auto color = sol::table::create(s.lua_state());
color["r"] = element.background_color[0];
color["g"] = element.background_color[1];
color["b"] = element.background_color[2];
color["a"] = element.background_color[3];
return color;
},
[](element& element, const sol::lua_table color)
{
element.background_color[0] = color["r"].get_type() == sol::type::number ? color["r"].get<float>() : 0.f;
element.background_color[1] = color["g"].get_type() == sol::type::number ? color["g"].get<float>() : 0.f;
element.background_color[2] = color["b"].get_type() == sol::type::number ? color["b"].get<float>() : 0.f;
element.background_color[3] = color["a"].get_type() == sol::type::number ? color["a"].get<float>() : 0.f;
}
);
element_type["bordercolor"] = sol::property(
[](element& element, const sol::this_state s)
{
auto color = sol::table::create(s.lua_state());
color["r"] = element.border_color[0];
color["g"] = element.border_color[1];
color["b"] = element.border_color[2];
color["a"] = element.border_color[3];
return color;
},
[](element& element, const sol::lua_table color)
{
element.border_color[0] = color["r"].get_type() == sol::type::number ? color["r"].get<float>() : 0.f;
element.border_color[1] = color["g"].get_type() == sol::type::number ? color["g"].get<float>() : 0.f;
element.border_color[2] = color["b"].get_type() == sol::type::number ? color["b"].get<float>() : 0.f;
element.border_color[3] = color["a"].get_type() == sol::type::number ? color["a"].get<float>() : 0.f;
}
);
element_type["borderwidth"] = sol::property(
[](element& element, const sol::this_state s)
{
auto color = sol::table::create(s.lua_state());
color["top"] = element.border_width[0];
color["right"] = element.border_width[1];
color["bottom"] = element.border_width[2];
color["left"] = element.border_width[3];
return color;
},
[](element& element, const sol::lua_table color)
{
element.border_width[0] = color["top"].get_type() == sol::type::number ? color["top"].get<float>() : 0.f;
element.border_width[1] = color["right"].get_type() == sol::type::number ? color["right"].get<float>() : element.border_width[1];
element.border_width[2] = color["bottom"].get_type() == sol::type::number ? color["bottom"].get<float>() : element.border_width[2];
element.border_width[3] = color["left"].get_type() == sol::type::number ? color["left"].get<float>() : element.border_width[3];
}
);
element_type["font"] = sol::property(
[](element& element)
{
return element.font;
},
[](element& element, const std::string& font)
{
element.set_font(font);
}
);
element_type["fontsize"] = sol::property(
[](element& element)
{
return element.fontsize;
},
[](element& element, float fontsize)
{
element.fontsize = (int)fontsize;
}
);
element_type["onnotify"] = [&handler](element& element, const std::string& event,
const event_callback& callback)
{
event_listener listener{};
listener.callback = callback;
listener.element = &element;
listener.event = event;
listener.is_volatile = false;
return handler.add_event_listener(std::move(listener));
};
element_type["onnotifyonce"] = [&handler](element& element, const std::string& event,
const event_callback& callback)
{
event_listener listener{};
listener.callback = callback;
listener.element = &element;
listener.event = event;
listener.is_volatile = true;
return handler.add_event_listener(std::move(listener));
};
element_type["notify"] = [&handler](element& element, const sol::this_state s, const std::string& _event,
sol::variadic_args va)
{
event event;
event.element = &element;
event.name = _event;
for (auto arg : va)
{
if (arg.get_type() == sol::type::number)
{
event.arguments.push_back(arg.as<int>());
}
if (arg.get_type() == sol::type::string)
{
event.arguments.push_back(arg.as<std::string>());
}
}
handler.dispatch(event);
};
auto menu_type = state.new_usertype<menu>("menu");
menu_type["onnotify"] = [&handler](menu& menu, const std::string& event,
const event_callback& callback)
{
event_listener listener{};
listener.callback = callback;
listener.element = &menu;
listener.event = event;
listener.is_volatile = false;
return handler.add_event_listener(std::move(listener));
};
menu_type["onnotifyonce"] = [&handler](menu& menu, const std::string& event,
const event_callback& callback)
{
event_listener listener{};
listener.callback = callback;
listener.element = &menu;
listener.event = event;
listener.is_volatile = true;
return handler.add_event_listener(std::move(listener));
};
menu_type["notify"] = [&handler](menu& element, const sol::this_state s, const std::string& _event,
sol::variadic_args va)
{
event event;
event.element = &element;
event.name = _event;
for (auto arg : va)
{
if (arg.get_type() == sol::type::number)
{
event.arguments.push_back(arg.as<int>());
}
if (arg.get_type() == sol::type::string)
{
event.arguments.push_back(arg.as<std::string>());
}
}
handler.dispatch(event);
};
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;
}
);
menu_type["isopen"] = [](menu& menu)
{
return menu.visible || (menu.type == menu_type::overlay && game::Menu_IsMenuOpenAndVisible(0, menu.overlay_menu.data()));
};
menu_type["open"] = [&handler](menu& menu)
{
event event;
event.element = &menu;
event.name = "close";
handler.dispatch(event);
menu.open();
};
menu_type["close"] = [&handler](menu& menu)
{
event event;
event.element = &menu;
event.name = "close";
handler.dispatch(event);
menu.close();
};
struct game
{
};
auto game_type = state.new_usertype<game>("game_");
state["game"] = game();
game_type["time"] = []()
{
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
return now.count();
};
game_type["newmenu"] = [](const sol::lua_value&, const std::string& name)
{
menus[name] = {};
return &menus[name];
};
game_type["newmenuoverlay"] = [](const sol::lua_value&, const std::string& name, const std::string& menu_name)
{
menus[name] = {};
menus[name].type = menu_type::overlay;
menus[name].overlay_menu = menu_name;
return &menus[name];
};
game_type["getmouseposition"] = [](const sol::this_state s, const game&)
{
auto pos = sol::table::create(s.lua_state());
pos["x"] = mouse[0];
pos["y"] = mouse[1];
return pos;
};
game_type["openmenu"] = [&handler](const game&, const std::string& name)
{
if (menus.find(name) == menus.end())
{
return;
}
const auto menu = &menus[name];
event event;
event.element = menu;
event.name = "close";
handler.dispatch(event);
menu->open();
};
game_type["closemenu"] = [&handler](const game&, const std::string& name)
{
if (menus.find(name) == menus.end())
{
return;
}
const auto menu = &menus[name];
event event;
event.element = menu;
event.name = "close";
handler.dispatch(event);
menu->close();
};
game_type["onframe"] = [&scheduler](const game&, const sol::protected_function& callback)
{
return scheduler.add(callback, 0, false);
};
game_type["ontimeout"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, true);
};
game_type["oninterval"] = [&scheduler](const game&, const sol::protected_function& callback,
const long long milliseconds)
{
return scheduler.add(callback, milliseconds, false);
};
game_type["onnotify"] = [&handler](const game&, const std::string& event,
const event_callback& callback)
{
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);
};
game_type["playsound"] = [](const game&, const std::string& sound)
{
::game::UI_PlayLocalSoundAlias(0, sound.data());
};
game_type["getwindowsize"] = [](const game&, const sol::this_state s)
{
const auto size = ::game::ScrPlace_GetViewPlacement()->realViewportSize;
auto screen = sol::table::create(s.lua_state());
screen["x"] = size[0];
screen["y"] = size[1];
return screen;
};
struct player
{
};
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");
}
::scheduler::once([s, name, args = std::vector<sol::object>(va.begin(), va.end())]()
{
std::vector<scripting::script_value> arguments{};
for (auto arg : args)
{
arguments.push_back(convert({s, arg}));
}
const auto player_value = scripting::call("getentbynum", {0});
if (player_value.get_raw().type != ::game::SCRIPT_OBJECT)
{
return;
}
const auto player = player_value.as<scripting::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();
};
state.script(animation_script);
}
}
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->event_handler_.clear();
this->state_ = {};
}
void context::run_frame()
{
this->scheduler_.run_frame();
this->state_.collect_garbage();
}
void context::notify(const event& e)
{
this->scheduler_.dispatch(e);
this->event_handler_.dispatch(e);
}
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));
}
}

View File

@ -0,0 +1,47 @@
#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
{
extern std::unordered_map<std::string, menu> menus;
extern std::vector<element*> elements;
extern element ui_element;
extern int mouse[2];
class context
{
public:
context(std::string folder);
~context();
context(context&&) noexcept = delete;
context& operator=(context&&) noexcept = delete;
context(const context&) = delete;
context& operator=(const context&) = delete;
void run_frame();
void notify(const event& e);
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);
};
}

View File

@ -0,0 +1,453 @@
#include <std_include.hpp>
#include "engine.hpp"
#include "context.hpp"
#include "../../../component/scheduler.hpp"
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace ui_scripting::lua::engine
{
void notify(const event& e);
namespace
{
float screen_max[2];
void check_resize()
{
screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0];
screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1];
}
int relative_mouse(int value)
{
return (int)(((float)value / screen_max[0]) * 1920.f);
}
int relative(int value)
{
return (int)(((float)value / 1920.f) * screen_max[0]);
}
float relative(float value)
{
return (value / 1920.f) * screen_max[0];
}
bool point_in_rect(int px, int py, int x, int y, int w, int h)
{
return (px > x && px < x + w && py > y && py < y + h);
}
bool is_menu_visible(const menu& menu)
{
return menu.visible || (menu.type == menu_type::overlay && game::Menu_IsMenuOpenAndVisible(0, menu.overlay_menu.data()));
}
std::vector<element*> elements_in_point(int x, int y)
{
std::vector<element*> result;
for (const auto& menu : menus)
{
if (!is_menu_visible(menu.second))
{
continue;
}
for (const auto& child : menu.second.children)
{
const auto in_rect = point_in_rect(
x, y,
(int)child->x,
(int)child->y,
(int)child->w + (int)child->border_width[1] + (int)child->border_width[3],
(int)child->h + (int)child->border_width[0] + (int)child->border_width[2]
);
if (in_rect)
{
result.push_back(child);
}
}
}
return result;
}
void handle_key_event(const int key, const int down)
{
const auto _elements = elements_in_point(mouse[0], mouse[1]);
switch (key)
{
case game::K_MOUSE2:
case game::K_MOUSE1:
{
const auto click_name = key == game::K_MOUSE1
? "click"
: "rightclick";
const auto key_name = key == game::K_MOUSE1
? "mouse"
: "rightmouse";
{
event main_event;
main_event.element = &ui_element;
main_event.name = utils::string::va("%s%s", key_name, down ? "down" : "up");
main_event.arguments =
{
mouse[0],
mouse[1],
};
engine::notify(main_event);
for (const auto& element : _elements)
{
event event;
event.element = element;
event.name = utils::string::va("%s%s", key_name, down ? "down" : "up");
event.arguments =
{
mouse[0],
mouse[1],
};
engine::notify(event);
}
}
if (!down)
{
event main_event;
main_event.element = &ui_element;
main_event.name = click_name;
main_event.arguments =
{
mouse[0],
mouse[1],
};
engine::notify(main_event);
for (const auto& element : _elements)
{
event event;
event.element = element;
event.name = click_name;
event.arguments =
{
mouse[0],
mouse[1],
};
engine::notify(event);
}
}
break;
}
case game::K_MWHEELUP:
case game::K_MWHEELDOWN:
{
const auto key_name = key == game::K_MWHEELUP
? "scrollup"
: "scrolldown";
if (!down)
{
break;
}
{
event main_event;
main_event.element = &ui_element;
main_event.name = key_name;
main_event.arguments =
{
mouse[0],
mouse[1],
};
engine::notify(main_event);
for (const auto& element : _elements)
{
event event;
event.element = element;
event.name = key_name;
event.arguments = {mouse[0], mouse[1]};
engine::notify(event);
}
}
break;
}
default:
{
event event;
event.element = &ui_element;
event.name = down
? "keydown"
: "keyup";
event.arguments = {key};
notify(event);
break;
}
}
}
void handle_char_event(const int key)
{
std::string key_str = {(char)key};
event event;
event.element = &ui_element;
event.name = "keypress";
event.arguments = {key_str};
engine::notify(event);
}
std::vector<element*> previous_elements;
void handle_mousemove_event(const int x, const int y)
{
if (mouse[0] == x && mouse[1] == y)
{
return;
}
mouse[0] = x;
mouse[1] = y;
{
event event;
event.element = &ui_element;
event.name = "mousemove";
event.arguments = {x, y};
engine::notify(event);
}
const auto _elements = elements_in_point(x, y);
for (const auto& element : _elements)
{
event event;
event.element = element;
event.name = "mouseover";
engine::notify(event);
}
for (const auto& element : previous_elements)
{
auto found = false;
for (const auto& _element : _elements)
{
if (element == _element)
{
found = true;
}
}
if (!found)
{
event event;
event.element = element;
event.name = "mouseleave";
engine::notify(event);
}
}
for (const auto& element : _elements)
{
auto found = false;
for (const auto& _element : previous_elements)
{
if (element == _element)
{
found = true;
}
}
if (!found)
{
event event;
event.element = element;
event.name = "mouseenter";
engine::notify(event);
}
}
previous_elements = _elements;
}
auto& get_scripts()
{
static std::vector<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;
}
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 render_menus()
{
check_resize();
for (const auto& menu : menus)
{
if (is_menu_visible(menu.second))
{
menu.second.render();
}
}
}
void close_all_menus()
{
for (auto& menu : menus)
{
if (!is_menu_visible(menu.second))
{
continue;
}
event event;
event.element = &menu.second;
event.name = "close";
notify(event);
menu.second.close();
}
}
void clear_menus()
{
menus.clear();
for (const auto element : elements)
{
delete element;
}
elements.clear();
}
}
void open_menu(const std::string& name)
{
if (menus.find(name) == menus.end())
{
return;
}
const auto menu = &menus[name];
event event;
event.element = menu;
event.name = "open";
notify(event);
menu->open();
}
void close_menu(const std::string& name)
{
if (menus.find(name) == menus.end())
{
return;
}
const auto menu = &menus[name];
event event;
event.element = menu;
event.name = "close";
notify(event);
menu->close();
}
void start()
{
close_all_menus();
get_scripts().clear();
clear_menus();
load_scripts();
}
void stop()
{
close_all_menus();
get_scripts().clear();
clear_menus();
}
void ui_event(const std::string& type, const std::vector<int>& arguments)
{
::scheduler::once([type, arguments]()
{
if (type == "key")
{
handle_key_event(arguments[0], arguments[1]);
}
if (type == "char")
{
handle_char_event(arguments[0]);
}
if (type == "mousemove")
{
handle_mousemove_event(relative_mouse(arguments[0]), relative_mouse(arguments[1]));
}
}, ::scheduler::pipeline::renderer);
}
void notify(const event& e)
{
for (auto& script : get_scripts())
{
script->notify(e);
}
}
void run_frame()
{
check_resize();
render_menus();
for (auto& script : get_scripts())
{
script->run_frame();
}
}
}

View File

@ -0,0 +1,13 @@
#pragma once
namespace ui_scripting::lua::engine
{
void start();
void stop();
void close_menu(const std::string& name);
void open_menu(const std::string& name);
void ui_event(const std::string&, const std::vector<int>&);
void run_frame();
}

View 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 (...)
{
}
}
}
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "context.hpp"
namespace ui_scripting::lua
{
void handle_error(const sol::protected_function_result& result);
}

View File

@ -0,0 +1,13 @@
#pragma once
#include "context.hpp"
namespace ui_scripting::lua
{
struct event
{
std::string name;
const void* element{};
std::vector<std::variant<int, std::string>> arguments;
};
}

View File

@ -0,0 +1,189 @@
#include "std_include.hpp"
#include "context.hpp"
#include "error.hpp"
#include "../../scripting/lua/value_conversion.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);
};
event_listener_handle_type["endon"] = [this](const event_listener_handle& handle, const element* entity, const std::string& event)
{
this->add_endon_condition(handle, entity, event);
};
}
void event_handler::dispatch(const event& event)
{
bool has_built_arguments = false;
event_arguments arguments{};
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
this->handle_endon_conditions(event);
for (auto i = tasks.begin(); i != tasks.end();)
{
if (i->event != event.name || i->element != event.element)
{
++i;
continue;
}
if (!i->is_deleted)
{
if (!has_built_arguments)
{
has_built_arguments = true;
arguments = this->build_arguments(event);
}
handle_error(i->callback(sol::as_args(arguments)));
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
event_listener_handle event_handler::add_event_listener(event_listener&& listener)
{
const uint64_t id = ++this->current_listener_id_;
listener.id = id;
listener.is_deleted = false;
new_callbacks_.access([&listener](task_list& tasks)
{
tasks.emplace_back(std::move(listener));
});
return {id};
}
void event_handler::add_endon_condition(const event_listener_handle& handle, const element* element,
const std::string& event)
{
auto merger = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.endon_conditions.emplace_back((uint64_t)element, event);
}
}
};
callbacks_.access([&](task_list& tasks)
{
merger(tasks);
new_callbacks_.access(merger);
});
}
void event_handler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
void event_handler::remove(const event_listener_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.is_deleted = true;
break;
}
}
};
callbacks_.access(mask_as_deleted);
new_callbacks_.access(mask_as_deleted);
}
void event_handler::merge_callbacks()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
std::move_iterator<task_list::iterator>(new_tasks.end()));
new_tasks = {};
});
});
}
void event_handler::handle_endon_conditions(const event& event)
{
auto deleter = [&](task_list& tasks)
{
for (auto& task : tasks)
{
for (auto& condition : task.endon_conditions)
{
if (condition.first == (uint64_t)event.element && condition.second == event.name)
{
task.is_deleted = true;
break;
}
}
}
};
callbacks_.access(deleter);
}
event_arguments event_handler::build_arguments(const event& event) const
{
event_arguments arguments;
for (const auto& argument : event.arguments)
{
const auto index = argument.index();
if (index == 0)
{
const sol::lua_value value = {this->state_, std::get<int>(argument)};
arguments.emplace_back(value);
}
if (index == 1)
{
const sol::lua_value value = {this->state_, std::get<std::string>(argument)};
arguments.emplace_back(value);
}
}
return arguments;
}
}

View File

@ -0,0 +1,59 @@
#pragma once
#include <utils/concurrency.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 = {};
void* element{};
event_callback callback = {};
bool is_volatile = false;
bool is_deleted = false;
std::vector<std::pair<uint64_t, std::string>> endon_conditions{};
};
class event_handler final
{
public:
event_handler(sol::state& state);
event_handler(event_handler&&) noexcept = delete;
event_handler& operator=(event_handler&&) noexcept = delete;
event_handler(const scheduler&) = delete;
event_handler& operator=(const event_handler&) = delete;
void dispatch(const event& event);
event_listener_handle add_event_listener(event_listener&& listener);
void clear();
private:
sol::state& state_;
std::atomic_int64_t current_listener_id_ = 0;
using task_list = std::vector<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();
void handle_endon_conditions(const event& event);
void add_endon_condition(const event_listener_handle& handle, const element* element, const std::string& event);
event_arguments build_arguments(const event& event) const;
};
}

View File

@ -0,0 +1,171 @@
#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);
};
task_handle_type["endon"] = [this](const task_handle& handle, const element* element, const std::string& event)
{
this->add_endon_condition(handle, element, event);
};
}
void scheduler::dispatch(const event& event)
{
auto deleter = [&](task_list& tasks)
{
for (auto& task : tasks)
{
for (auto& condition : task.endon_conditions)
{
if (condition.first == (uint64_t)event.element && condition.second == event.name)
{
task.is_deleted = true;
break;
}
}
}
};
callbacks_.access([&](task_list& tasks)
{
deleter(tasks);
new_callbacks_.access(deleter);
});
}
void scheduler::run_frame()
{
callbacks_.access([&](task_list& tasks)
{
this->merge_callbacks();
for (auto i = tasks.begin(); i != tasks.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
const auto diff = now - i->last_call;
if (diff < i->delay)
{
++i;
continue;
}
i->last_call = now;
if (!i->is_deleted)
{
handle_error(i->callback());
}
if (i->is_volatile || i->is_deleted)
{
i = tasks.erase(i);
}
else
{
++i;
}
}
});
}
void scheduler::clear()
{
callbacks_.access([&](task_list& tasks)
{
new_callbacks_.access([&](task_list& new_tasks)
{
new_tasks.clear();
tasks.clear();
});
});
}
task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds,
const bool is_volatile)
{
return this->add(callback, std::chrono::milliseconds(milliseconds), is_volatile);
}
task_handle scheduler::add(const sol::protected_function& callback, const std::chrono::milliseconds delay,
const bool is_volatile)
{
const uint64_t id = ++this->current_task_id_;
task task;
task.is_volatile = is_volatile;
task.callback = callback;
task.delay = delay;
task.last_call = std::chrono::steady_clock::now();
task.id = id;
task.is_deleted = false;
new_callbacks_.access([&task](task_list& tasks)
{
tasks.emplace_back(std::move(task));
});
return {id};
}
void scheduler::add_endon_condition(const task_handle& handle, const element* element, const std::string& event)
{
auto merger = [&](task_list& tasks)
{
for (auto& task : tasks)
{
if (task.id == handle.id)
{
task.endon_conditions.emplace_back(element->id, event);
}
}
};
callbacks_.access([&](task_list& tasks)
{
merger(tasks);
new_callbacks_.access(merger);
});
}
void scheduler::remove(const task_handle& handle)
{
auto mask_as_deleted = [&](task_list& tasks)
{
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 = {};
});
});
}
}

View File

@ -0,0 +1,54 @@
#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;
std::vector<std::pair<uint64_t, std::string>> endon_conditions{};
};
class scheduler final
{
public:
scheduler(sol::state& state);
scheduler(scheduler&&) noexcept = delete;
scheduler& operator=(scheduler&&) noexcept = delete;
scheduler(const scheduler&) = delete;
scheduler& operator=(const scheduler&) = delete;
void dispatch(const event& event);
void run_frame();
void clear();
task_handle add(const sol::protected_function& callback, long long milliseconds, bool is_volatile);
task_handle add(const sol::protected_function& callback, std::chrono::milliseconds delay, bool is_volatile);
private:
using task_list = std::vector<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 add_endon_condition(const task_handle& handle, const element* element, const std::string& event);
void remove(const task_handle& handle);
void merge_callbacks();
};
}

View File

@ -0,0 +1,41 @@
#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()
{
*game::keyCatchers |= 0x40;
this->visible = true;
}
void menu::close()
{
*game::keyCatchers &= ~0x40;
this->visible = false;
}
void menu::render() const
{
if (this->cursor)
{
*game::keyCatchers |= 0x40;
}
for (auto& element : this->children)
{
element->render();
}
}
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "game/game.hpp"
#include "element.hpp"
namespace ui_scripting
{
enum menu_type
{
normal,
overlay
};
class menu final
{
public:
menu();
bool visible = false;
bool cursor = false;
void open();
void close();
void add_child(element* el);
void render() const;
menu_type type = normal;
std::string overlay_menu;
std::vector<element*> children{};
};
}

View File

@ -20,3 +20,5 @@
#define RUNNER 312
#define ICON_IMAGE 313
#define LUA_ANIMATION_SCRIPT 314

View File

@ -97,6 +97,8 @@ ID_ICON ICON "resources/icon.ico"
MENU_MAIN RCDATA "resources/main.html"
LUA_ANIMATION_SCRIPT RCDATA "resources/animation.lua"
#ifdef _DEBUG
TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll"
#else

View File

@ -0,0 +1,92 @@
function element:animate(name, state, animationtime)
local start = {
x = self.x,
y = self.y,
w = self.w,
h = self.h,
color = self.color,
backcolor = self.backcolor,
bordercolor = self.bordercolor,
borderwidth = self.borderwidth,
fontsize = self.fontsize
}
local _end = {}
for k, v in pairs(start) do
_end[k] = state[k] or v
end
local diffs = {}
for k, v in pairs(_end) do
if (type(v) == "table") then
local value = {}
local different = false
for _k, _v in pairs(v) do
value[_k] = _v - start[k][_k]
if (value[_k] ~= 0) then
different = true
end
end
if (different) then
diffs[k] = value
end
else
local value = v - start[k]
if (value ~= 0) then
diffs[k] = v - start[k]
end
end
end
local timeout = nil
local interval = nil
local starttime = game:time()
interval = game:onframe(function()
local time = game:time()
local percentage = (time - starttime) / animationtime
if (percentage >= 1) then
for k, v in pairs(diffs) do
self[k] = _end[k]
end
else
for k, v in pairs(diffs) do
if (type(v) == "table") then
local value = {}
for _k, _v in pairs(v) do
value[_k] = start[k][_k] + _v * percentage
end
self[k] = value
else
self[k] = start[k] + v * percentage
end
end
end
end)
timeout = game:ontimeout(function()
interval:clear()
for k, v in pairs(diffs) do
self[k] = _end[k]
end
end, animationtime)
self:onnotifyonce("cancel_animation", function(_name)
if (name == _name) then
timeout:clear()
interval:clear()
end
end)
end
function element:cancelanimations(name, callback)
self:notify("cancel_animation", name)
if (type(callback) == "function") then
game:ontimeout(callback, 0)
end
end