#include <std_include.hpp> #include "engine.hpp" #include "context.hpp" #include "../../../component/scheduler.hpp" #include "../../../component/ui_scripting.hpp" #include <utils/io.hpp> #include <utils/string.hpp> #include <utils/nt.hpp> namespace ui_scripting::lua::engine { namespace { const auto updater_script = utils::nt::load_resource(LUI_UPDATER_MENU); 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)ceil(((float)value / screen_max[0]) * 1920.f); } int relative(int value) { return (int)ceil(((float)value / 1920.f) * screen_max[0]); } float relative(float value) { return ceil((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.hidden || (!menu.hidden && 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) || menu.second.ignoreevents) { continue; } for (const auto& child : menu.second.children) { if (child->hidden) { continue; } 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}; engine::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 std::string& script_dir) { 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, script_type::file)); } } } void load_code(const std::string& code) { get_scripts().push_back(std::make_unique<context>(code, script_type::code)); } void render_menus() { check_resize(); for (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"; engine::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"; engine::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"; engine::notify(event); menu->close(); } void start() { clear_converted_functions(); close_all_menus(); get_scripts().clear(); clear_menus(); load_code(updater_script); load_scripts("ui_scripts/"); load_scripts("h2-mod/ui_scripts/"); load_scripts("data/ui_scripts/"); if (!game::mod_folder.empty()) { load_scripts(utils::string::va("%s/ui_scripts/", game::mod_folder.data())); } } void stop() { clear_converted_functions(); close_all_menus(); get_scripts().clear(); clear_menus(); } void ui_event(const std::string& type, const std::vector<int>& arguments) { if (type == "key") { handle_key_event(arguments[0], arguments[1]); } if (type == "char") { handle_char_event(arguments[0]); } if (type == "mousemove") { handle_mousemove_event(relative_mouse(arguments[0]), relative_mouse(arguments[1])); } } void notify(const event& e) { for (auto& script : get_scripts()) { script->notify(e); } } void run_frame() { check_resize(); render_menus(); for (auto& script : get_scripts()) { script->run_frame(); } } }