diff --git a/data/ui_scripts/discord/__init__.lua b/data/ui_scripts/discord/__init__.lua new file mode 100644 index 00000000..cd2e70fc --- /dev/null +++ b/data/ui_scripts/discord/__init__.lua @@ -0,0 +1,272 @@ +if (game:issingleplayer() or Engine.InFrontend()) then + return +end + +local container = LUI.UIVerticalList.new({ + topAnchor = true, + rightAnchor = true, + top = 20, + right = 200, + width = 200, + spacing = 5, +}) + +function canasktojoin(userid) + history = history or {} + if (history[userid] ~= nil) then + return false + end + + history[userid] = true + game:ontimeout(function() + history[userid] = nil + end, 15000) + + return true +end + +function truncatename(name, length) + if (#name <= length - 3) then + return name + end + + return name:sub(1, length - 3) .. "..." +end + +function addrequest(request) + if (not canasktojoin(request.userid)) then + return + end + + if (container.temp) then + container:removeElement(container.temp) + container.temp = nil + end + + local invite = LUI.UIElement.new({ + leftAnchor = true, + rightAnchor = true, + height = 75, + }) + + invite:registerAnimationState("move_in", { + leftAnchor = true, + height = 75, + width = 200, + left = -220, + }) + + invite:animateToState("move_in", 100) + + local background = LUI.UIImage.new({ + topAnchor = true, + leftAnchor = true, + rightAnchor = true, + bottomAnchor = true, + top = 1, + left = 1, + bottom = -1, + right = -1, + material = luiglobals.RegisterMaterial("white"), + color = { + r = 0, + b = 0, + g = 0, + }, + alpha = 0.6, + }) + + local border = LUI.UIImage.new({ + topAnchor = true, + leftAnchor = true, + rightAnchor = true, + bottomAnchor = true, + material = luiglobals.RegisterMaterial("btn_focused_rect_innerglow"), + }) + + border:setup9SliceImage(10, 5, 0.25, 0.12) + + local paddingvalue = 10 + local padding = LUI.UIElement.new({ + topAnchor = true, + leftAnchor = true, + rightAnchor = true, + bottomAnchor = true, + top = paddingvalue, + left = paddingvalue, + right = -paddingvalue, + bottom = -paddingvalue, + }) + + local avatarmaterial = discord.getavatarmaterial(request.userid) + local avatar = LUI.UIImage.new({ + leftAnchor = true, + topAnchor = true, + width = 32, + height = 32, + left = 1, + material = luiglobals.RegisterMaterial(avatarmaterial) + }) + + local username = LUI.UIText.new({ + leftAnchor = true, + topAnchor = true, + height = 12, + left = 32 + paddingvalue, + color = luiglobals.Colors.white, + alignment = LUI.Alignment.Left, + rightAnchor = true, + font = CoD.TextSettings.BodyFontBold.Font + }) + + username:setText(string.format("%s^7#%s requested to join your game!", + truncatename(request.username, 18), request.discriminator)) + + local buttons = LUI.UIElement.new({ + leftAnchor = true, + rightAnchor = true, + topAnchor = true, + top = 37, + height = 18, + }) + + local createbutton = function(text, left) + local button = LUI.UIElement.new({ + leftAnchor = left, + rightAnchor = not left, + topAnchor = true, + height = 18, + width = 85, + material = luiglobals.RegisterMaterial("btn_focused_rect_innerglow"), + }) + + local center = LUI.UIText.new({ + rightAnchor = true, + height = 12, + width = 85, + top = -6.5, + alignment = LUI.Alignment.Center, + font = CoD.TextSettings.BodyFontBold.Font + }) + + button:setup9SliceImage(10, 5, 0.25, 0.12) + center:setText(text) + button:addElement(center) + + return button + end + + buttons:addElement(createbutton("[F1] Accept", true)) + buttons:addElement(createbutton("[F2] Deny")) + + local fadeouttime = 50 + local timeout = 10 * 1000 - fadeouttime + + local function close() + container:processEvent({ + name = "update_navigation", + dispatchToChildren = true + }) + invite:animateToState("fade_out", fadeouttime) + invite:addElement(LUI.UITimer.new(fadeouttime + 50, "remove")) + + invite:registerEventHandler("remove", function() + container:removeElement(invite) + if (container.temp) then + container:removeElement(container.temp) + container.temp = nil + end + local temp = LUI.UIElement.new({}) + container.temp = temp + container:addElement(temp) + end) + end + + buttons:registerEventHandler("keydown_", function(element, event) + if (event.key == "F1") then + close() + discord.respond(request.userid, discord.reply.yes) + end + + if (event.key == "F2") then + close() + discord.respond(request.userid, discord.reply.no) + end + end) + + invite:registerAnimationState("fade_out", { + leftAnchor = true, + rightAnchor = true, + height = 75, + alpha = 0, + left = 0 + }) + + invite:addElement(LUI.UITimer.new(timeout, "end_invite")) + invite:registerEventHandler("end_invite", function() + close() + discord.respond(request.userid, discord.reply.ignore) + end) + + local bar = LUI.UIImage.new({ + bottomAnchor = true, + leftAnchor = true, + bottom = -3, + left = 3, + width = 200 - 6, + material = luiglobals.RegisterMaterial("white"), + height = 2, + color = { + r = 92 / 255, + g = 206 / 255, + b = 113 / 255, + } + }) + + bar:registerAnimationState("closing", { + bottomAnchor = true, + leftAnchor = true, + bottom = -3, + left = 3, + width = 0, + height = 2, + }) + + bar:animateToState("closing", timeout) + + avatar:registerEventHandler("update", function() + local avatarmaterial = discord.getavatarmaterial(request.userid) + avatar:setImage(luiglobals.RegisterMaterial(avatarmaterial)) + end) + + avatar:addElement(LUI.UITimer.new(100, "update")) + + invite:addElement(background) + invite:addElement(bar) + invite:addElement(border) + invite:addElement(padding) + padding:addElement(username) + padding:addElement(avatar) + padding:addElement(buttons) + + container:addElement(invite) +end + +container:registerEventHandler("keydown", function(element, event) + local first = container:getFirstChild() + + if (not first) then + return + end + + first:processEvent({ + name = "keydown_", + key = event.key + }) +end) + +LUI.roots.UIRoot0:registerEventHandler("discord_join_request", function(element, event) + addrequest(event.request) +end) + +LUI.roots.UIRoot0:addElement(container) diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp index 5925a64a..3698e30f 100644 --- a/src/client/component/discord.cpp +++ b/src/client/component/discord.cpp @@ -7,11 +7,24 @@ #include "command.hpp" #include "network.hpp" #include "party.hpp" +#include "materials.hpp" +#include "discord.hpp" + +#include "ui_scripting.hpp" +#include "game/ui_scripting/execution.hpp" #include +#include +#include #include +#define DEFAULT_AVATAR "discord_default_avatar" +#define AVATAR "discord_avatar_%s" + +#define DEFAULT_AVATAR_URL "https://cdn.discordapp.com/embed/avatars/0.png" +#define AVATAR_URL "https://cdn.discordapp.com/avatars/%s/%s.png?size=128" + namespace discord { namespace @@ -20,15 +33,13 @@ namespace discord void update_discord() { - Discord_RunCallbacks(); - if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded()) { - discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer"; + discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer"); discord_presence.state = "Main Menu"; - auto firingRangeDvar = game::Dvar_FindVar("virtualLobbyInFiringRange"); - if (firingRangeDvar && firingRangeDvar->current.enabled == 1) + const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange"); + if (in_firing_range && in_firing_range->current.enabled == 1) { discord_presence.state = "Firing Range"; } @@ -36,20 +47,28 @@ namespace discord discord_presence.partySize = 0; discord_presence.partyMax = 0; discord_presence.startTimestamp = 0; - discord_presence.largeImageKey = game::environment::is_sp() ? "menu_singleplayer" : "menu_multiplayer"; + discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer"); + + // set to blank when in lobby + discord_presence.matchSecret = ""; + discord_presence.joinSecret = ""; + discord_presence.partyId = ""; + discord_presence.state = ""; } else { + static char details[0x80] = {0}; const auto map = game::Dvar_FindVar("mapname")->current.string; - const auto mapname = game::UI_SafeTranslateString(utils::string::va("PRESENCE_%s%s", (game::environment::is_sp() ? "SP_" : ""), map)); + const auto mapname = game::UI_SafeTranslateString( + utils::string::va("PRESENCE_%s%s", SELECT_VALUE("SP_", ""), map)); if (game::environment::is_mp()) { - const auto gametype = game::UI_GetGameTypeDisplayName(game::Dvar_FindVar("g_gametype")->current.string); + const auto gametype = game::UI_GetGameTypeDisplayName( + game::Dvar_FindVar("g_gametype")->current.string); + strcpy_s(details, 0x80, utils::string::va("%s on %s", gametype, mapname)); - discord_presence.details = utils::string::va("%s on %s", gametype, mapname); - - char clean_hostname[0x100] = {0}; + static char clean_hostname[0x80] = {0}; utils::string::strip(game::Dvar_FindVar("sv_hostname")->current.string, clean_hostname, sizeof(clean_hostname)); auto max_clients = party::server_client_count(); @@ -59,6 +78,29 @@ namespace discord { strcpy_s(clean_hostname, "Private Match"); max_clients = game::Dvar_FindVar("sv_maxclients")->current.integer; + discord_presence.partyPrivacy = DISCORD_PARTY_PRIVATE; + } + else + { + const auto server_net_info = party::get_state_host(); + const auto server_ip_port = utils::string::va("%i.%i.%i.%i:%i", + static_cast(server_net_info.ip[0]), + static_cast(server_net_info.ip[1]), + static_cast(server_net_info.ip[2]), + static_cast(server_net_info.ip[3]), + static_cast(ntohs(server_net_info.port)) + ); + + static char join_secret[0x80] = {0}; + strcpy_s(join_secret, 0x80, server_ip_port); + + static char party_id[0x80] = {0}; + const auto server_ip_port_hash = utils::cryptography::sha1::compute(server_ip_port, true).substr(0, 8); + strcpy_s(party_id, 0x80, server_ip_port_hash.data()); + + discord_presence.partyId = party_id; + discord_presence.joinSecret = join_secret; + discord_presence.partyPrivacy = DISCORD_PARTY_PUBLIC; } discord_presence.partySize = *reinterpret_cast(0x1429864C4); @@ -70,9 +112,11 @@ namespace discord { discord_presence.state = ""; discord_presence.largeImageKey = map; - discord_presence.details = mapname; + strcpy_s(details, 0x80, mapname); } + discord_presence.details = details; + if (!discord_presence.startTimestamp) { discord_presence.startTimestamp = std::chrono::duration_cast( @@ -82,6 +126,51 @@ namespace discord Discord_UpdatePresence(&discord_presence); } + + void download_user_avatar(const std::string& id, const std::string& avatar) + { + const auto data = utils::http::get_data( + utils::string::va(AVATAR_URL, id.data(), avatar.data())); + if (data.has_value()) + { + materials::add(utils::string::va(AVATAR, id.data()), data.value()); + } + } + + bool has_default_avatar = false; + void download_default_avatar() + { + const auto data = utils::http::get_data(DEFAULT_AVATAR_URL); + if (data.has_value()) + { + has_default_avatar = true; + materials::add(DEFAULT_AVATAR, data.value()); + } + } + } + + std::string get_avatar_material(const std::string& id) + { + const auto avatar_name = utils::string::va(AVATAR, id.data()); + if (materials::exists(avatar_name)) + { + return avatar_name; + } + + if (has_default_avatar) + { + return DEFAULT_AVATAR; + } + + return "black"; + } + + void respond(const std::string& id, int reply) + { + scheduler::once([=]() + { + Discord_Respond(id.data(), reply); + }, scheduler::pipeline::async); } class component final : public component_interface @@ -99,16 +188,19 @@ namespace discord handlers.ready = ready; handlers.errored = errored; handlers.disconnected = errored; - handlers.joinGame = nullptr; + handlers.joinGame = join_game; handlers.spectateGame = nullptr; - handlers.joinRequest = nullptr; + handlers.joinRequest = join_request; Discord_Initialize("947125042930667530", &handlers, 1, nullptr); + scheduler::once(download_default_avatar, scheduler::pipeline::async); + scheduler::once([]() { scheduler::once(update_discord, scheduler::pipeline::async); scheduler::loop(update_discord, scheduler::pipeline::async, 5s); + scheduler::loop(Discord_RunCallbacks, scheduler::pipeline::async, 1s); }, scheduler::pipeline::main); initialized_ = true; @@ -127,14 +219,11 @@ namespace discord private: bool initialized_ = false; - static void ready(const DiscordUser* /*request*/) + static void ready(const DiscordUser* request) { ZeroMemory(&discord_presence, sizeof(discord_presence)); - discord_presence.instance = 1; - - console::info("Discord: Ready\n"); - + console::info("Discord: Ready on %s (%s)\n", request->username, request->userId); Discord_UpdatePresence(&discord_presence); } @@ -142,6 +231,57 @@ namespace discord { console::error("Discord: Error (%i): %s\n", error_code, message); } + + static void join_game(const char* join_secret) + { + console::info("Discord: Join game called with join secret: %s\n", join_secret); + + std::string secret = join_secret; + scheduler::once([=]() + { + game::netadr_s target{}; + if (game::NET_StringToAdr(secret.data(), &target)) + { + console::info("Discord: Connecting to server: %s\n", secret.data()); + party::connect(target); + } + }, scheduler::pipeline::main); + } + + static void join_request(const DiscordUser* request) + { + console::info("Discord: join_request from %s (%s)\n", request->username, request->userId); + + if (game::Com_InFrontend() || !ui_scripting::lui_running()) + { + Discord_Respond(request->userId, DISCORD_REPLY_IGNORE); + return; + } + + std::string user_id = request->userId; + std::string avatar = request->avatar; + std::string discriminator = request->discriminator; + std::string username = request->username; + + scheduler::once([=]() + { + const ui_scripting::table request_table{}; + request_table.set("avatar", avatar); + request_table.set("discriminator", discriminator); + request_table.set("userid", user_id); + request_table.set("username", username); + + ui_scripting::notify("discord_join_request", + { + {"request", request_table} + }); + }, scheduler::pipeline::lui); + + if (!materials::exists(utils::string::va(AVATAR, user_id.data()))) + { + download_user_avatar(user_id, avatar); + } + } }; } diff --git a/src/client/component/discord.hpp b/src/client/component/discord.hpp new file mode 100644 index 00000000..5399f952 --- /dev/null +++ b/src/client/component/discord.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace discord +{ + std::string get_avatar_material(const std::string& id); + void respond(const std::string& id, int reply); +} diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index 7801bcbd..43aff87c 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -4,6 +4,7 @@ #include "game/game.hpp" #include "game_console.hpp" +#include "ui_scripting.hpp" #include "game/ui_scripting/execution.hpp" #include @@ -15,14 +16,10 @@ namespace input utils::hook::detour cl_char_event_hook; utils::hook::detour cl_key_event_hook; - bool lui_running() - { - return *game::hks::lua_state != nullptr; - } void cl_char_event_stub(const int local_client_num, const int key) { - if (lui_running()) + if (ui_scripting::lui_running()) { ui_scripting::notify("keypress", { @@ -41,7 +38,7 @@ namespace input void cl_key_event_stub(const int local_client_num, const int key, const int down) { - if (lui_running()) + if (ui_scripting::lui_running()) { ui_scripting::notify(down ? "keydown" : "keyup", { diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp index 4fbb3ed8..c5fed9a2 100644 --- a/src/client/component/materials.cpp +++ b/src/client/component/materials.cpp @@ -166,6 +166,14 @@ namespace materials }); } + bool exists(const std::string& name) + { + return material_data.access([&](material_data_t& data_) + { + return data_.images.find(name) != data_.images.end(); + }); + } + void clear() { material_data.access([&](material_data_t& data_) diff --git a/src/client/component/materials.hpp b/src/client/component/materials.hpp index 3a548548..0c02227f 100644 --- a/src/client/component/materials.hpp +++ b/src/client/component/materials.hpp @@ -3,5 +3,6 @@ namespace materials { void add(const std::string& name, const std::string& data); + bool exists(const std::string& name); void clear(); } diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 38dd7277..5a88eb65 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -234,6 +234,16 @@ namespace party network::send(target, "getInfo", connect_state.challenge); } + game::netadr_s get_state_host() + { + return connect_state.host; + } + + std::string get_state_challenge() + { + return connect_state.challenge; + } + void start_map(const std::string& mapname) { if (game::Live_SyncOnlineDataFlags(0) > 32) diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp index 0feb2e98..13990aea 100644 --- a/src/client/component/party.hpp +++ b/src/client/component/party.hpp @@ -9,6 +9,8 @@ namespace party void start_map(const std::string& mapname); void clear_sv_motd(); + game::netadr_s get_state_host(); + std::string get_state_challenge(); int server_client_count(); int get_client_num_by_name(const std::string& name); diff --git a/src/client/component/scheduler.cpp b/src/client/component/scheduler.cpp index de9b4983..68d82dd1 100644 --- a/src/client/component/scheduler.cpp +++ b/src/client/component/scheduler.cpp @@ -88,6 +88,7 @@ namespace scheduler utils::hook::detour r_end_frame_hook; utils::hook::detour g_run_frame_hook; utils::hook::detour main_frame_hook; + utils::hook::detour hks_frame_hook; void execute(const pipeline type) { @@ -112,6 +113,15 @@ namespace scheduler main_frame_hook.invoke(); execute(pipeline::main); } + + void hks_frame_stub() + { + const auto state = *game::hks::lua_state; + if (state) + { + execute(pipeline::lui); + } + } } void schedule(const std::function& callback, const pipeline type, @@ -183,6 +193,7 @@ namespace scheduler r_end_frame_hook.create(SELECT_VALUE(0x1404F7310, 0x1405FE470), scheduler::r_end_frame_stub); g_run_frame_hook.create(SELECT_VALUE(0x1402772D0, 0x14033A640), scheduler::server_frame_stub); main_frame_hook.create(SELECT_VALUE(0x1401CE8D0, 0x1400D8310), scheduler::main_frame_stub); + hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), scheduler::hks_frame_stub); } void pre_destroy() override diff --git a/src/client/component/scheduler.hpp b/src/client/component/scheduler.hpp index f26c60ce..68f78d54 100644 --- a/src/client/component/scheduler.hpp +++ b/src/client/component/scheduler.hpp @@ -16,6 +16,9 @@ namespace scheduler // The game's main thread main, + // LUI context + lui, + count, }; diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index c4f6d06f..c6b44883 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -93,6 +93,7 @@ namespace ui_scripting void hks_shutdown_stub() { + converted_functions.clear(); ui_scripting::lua::engine::stop(); hks_shutdown_hook.invoke(); } @@ -107,15 +108,6 @@ namespace ui_scripting return hks_allocator_hook.invoke(userData, oldMemory, oldSize, newSize); } - - void hks_frame_stub() - { - const auto state = *game::hks::lua_state; - if (state) - { - ui_scripting::lua::engine::run_frame(); - } - } } int main_function_handler(game::hks::lua_State* state) @@ -132,7 +124,7 @@ namespace ui_scripting return 0; } - const auto function = converted_functions[closure]; + const auto& function = converted_functions[closure]; const auto count = static_cast(state->m_apistack.top - state->m_apistack.base); const auto arguments = get_return_values(count); const auto s = function.lua_state(); @@ -175,6 +167,11 @@ namespace ui_scripting error_hook_enabled = false; } + bool lui_running() + { + return *game::hks::lua_state != nullptr; + } + class component final : public component_interface { public: @@ -186,11 +183,12 @@ namespace ui_scripting return; } + scheduler::loop(ui_scripting::lua::engine::run_frame, scheduler::pipeline::lui); + hks_start_hook.create(SELECT_VALUE(0x1400E4B40, 0x140176A40), hks_start_stub); hks_shutdown_hook.create(SELECT_VALUE(0x1400DD3D0, 0x14016CA80), hks_shutdown_stub); hksi_lual_error_hook.create(SELECT_VALUE(0x1400A5EA0, 0x14012F300), hksi_lual_error_stub); hks_allocator_hook.create(SELECT_VALUE(0x14009B570, 0x14012BAC0), hks_allocator_stub); - hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), hks_frame_stub); lui_error_hook.create(SELECT_VALUE(0x14007D7D0, 0x14010C9E0), lui_error_stub); hksi_hks_error_hook.create(SELECT_VALUE(0x14009DD80, 0x14012E390), hksi_hks_error_stub); diff --git a/src/client/component/ui_scripting.hpp b/src/client/component/ui_scripting.hpp index 2a48f6ec..b95710b3 100644 --- a/src/client/component/ui_scripting.hpp +++ b/src/client/component/ui_scripting.hpp @@ -9,4 +9,6 @@ namespace ui_scripting void enable_error_hook(); void disable_error_hook(); + + bool lui_running(); } \ No newline at end of file diff --git a/src/client/game/ui_scripting/lua/context.cpp b/src/client/game/ui_scripting/lua/context.cpp index d9a930f9..4a6294b6 100644 --- a/src/client/game/ui_scripting/lua/context.cpp +++ b/src/client/game/ui_scripting/lua/context.cpp @@ -14,10 +14,13 @@ #include "../../../component/fastfiles.hpp" #include "../../../component/scripting.hpp" #include "../../../component/mods.hpp" +#include "../../../component/discord.hpp" #include "component/game_console.hpp" #include "component/scheduler.hpp" +#include + #include #include #include @@ -265,36 +268,6 @@ namespace ui_scripting::lua return sol::as_returns(returns); }; - state["luiglobals"] = table((*::game::hks::lua_state)->globals.v.table); - state["CoD"] = state["luiglobals"]["CoD"]; - state["LUI"] = state["luiglobals"]["LUI"]; - state["Engine"] = state["luiglobals"]["Engine"]; - state["Game"] = state["luiglobals"]["Game"]; - - auto updater_table = sol::table::create(state.lua_state()); - - updater_table["relaunch"] = updater::relaunch; - - updater_table["sethastriedupdate"] = updater::set_has_tried_update; - updater_table["gethastriedupdate"] = updater::get_has_tried_update; - updater_table["autoupdatesenabled"] = updater::auto_updates_enabled; - - updater_table["startupdatecheck"] = updater::start_update_check; - updater_table["isupdatecheckdone"] = updater::is_update_check_done; - updater_table["getupdatecheckstatus"] = updater::get_update_check_status; - updater_table["isupdateavailable"] = updater::is_update_available; - - updater_table["startupdatedownload"] = updater::start_update_download; - updater_table["isupdatedownloaddone"] = updater::is_update_download_done; - updater_table["getupdatedownloadstatus"] = updater::get_update_download_status; - updater_table["cancelupdate"] = updater::cancel_update; - updater_table["isrestartrequired"] = updater::is_restart_required; - - updater_table["getlasterror"] = updater::get_last_error; - updater_table["getcurrentfile"] = updater::get_current_file; - - state["updater"] = updater_table; - if (::game::environment::is_sp()) { struct player @@ -339,6 +312,47 @@ namespace ui_scripting::lua }, ::scheduler::pipeline::server); }; } + + state["luiglobals"] = table((*::game::hks::lua_state)->globals.v.table); + state["CoD"] = state["luiglobals"]["CoD"]; + state["LUI"] = state["luiglobals"]["LUI"]; + state["Engine"] = state["luiglobals"]["Engine"]; + state["Game"] = state["luiglobals"]["Game"]; + + auto updater_table = sol::table::create(state.lua_state()); + + updater_table["relaunch"] = updater::relaunch; + + updater_table["sethastriedupdate"] = updater::set_has_tried_update; + updater_table["gethastriedupdate"] = updater::get_has_tried_update; + updater_table["autoupdatesenabled"] = updater::auto_updates_enabled; + + updater_table["startupdatecheck"] = updater::start_update_check; + updater_table["isupdatecheckdone"] = updater::is_update_check_done; + updater_table["getupdatecheckstatus"] = updater::get_update_check_status; + updater_table["isupdateavailable"] = updater::is_update_available; + + updater_table["startupdatedownload"] = updater::start_update_download; + updater_table["isupdatedownloaddone"] = updater::is_update_download_done; + updater_table["getupdatedownloadstatus"] = updater::get_update_download_status; + updater_table["cancelupdate"] = updater::cancel_update; + updater_table["isrestartrequired"] = updater::is_restart_required; + + updater_table["getlasterror"] = updater::get_last_error; + updater_table["getcurrentfile"] = updater::get_current_file; + + state["updater"] = updater_table; + + auto discord_table = sol::table::create(state.lua_state()); + + discord_table["respond"] = discord::respond; + discord_table["getavatarmaterial"] = discord::get_avatar_material; + discord_table["reply"] = sol::table::create(state.lua_state()); + discord_table["reply"]["yes"] = DISCORD_REPLY_YES; + discord_table["reply"]["ignore"] = DISCORD_REPLY_IGNORE; + discord_table["reply"]["no"] = DISCORD_REPLY_NO; + + state["discord"] = discord_table; } } diff --git a/src/client/game/ui_scripting/types.cpp b/src/client/game/ui_scripting/types.cpp index 66e8d497..4323df0a 100644 --- a/src/client/game/ui_scripting/types.cpp +++ b/src/client/game/ui_scripting/types.cpp @@ -13,6 +13,16 @@ namespace ui_scripting { } + bool lightuserdata::operator==(const lightuserdata& other) + { + return this->ptr == other.ptr; + } + + bool lightuserdata::operator!=(const lightuserdata& other) + { + return this->ptr != other.ptr; + } + /*************************************************************** * Userdata **************************************************************/ @@ -66,6 +76,16 @@ namespace ui_scripting return *this; } + bool userdata::operator==(const userdata& other) + { + return this->ptr == other.ptr; + } + + bool userdata::operator!=(const userdata& other) + { + return this->ptr != other.ptr; + } + void userdata::add() { game::hks::HksObject value{}; @@ -158,6 +178,16 @@ namespace ui_scripting return *this; } + bool table::operator==(const table& other) + { + return this->ptr == other.ptr; + } + + bool table::operator!=(const table& other) + { + return this->ptr != other.ptr; + } + void table::add() { game::hks::HksObject value{}; @@ -247,6 +277,16 @@ namespace ui_scripting return *this; } + bool function::operator==(const function& other) + { + return this->ptr == other.ptr; + } + + bool function::operator!=(const function& other) + { + return this->ptr != other.ptr; + } + void function::add() { game::hks::HksObject value{}; diff --git a/src/client/game/ui_scripting/types.hpp b/src/client/game/ui_scripting/types.hpp index bc2f7216..0eb4aeda 100644 --- a/src/client/game/ui_scripting/types.hpp +++ b/src/client/game/ui_scripting/types.hpp @@ -8,6 +8,10 @@ namespace ui_scripting { public: lightuserdata(void*); + + bool operator==(const lightuserdata& other); + bool operator!=(const lightuserdata& other); + void* ptr; }; @@ -24,6 +28,9 @@ namespace ui_scripting userdata& operator=(const userdata& other); userdata& operator=(userdata&& other) noexcept; + bool operator==(const userdata& other); + bool operator!=(const userdata& other); + script_value get(const script_value& key) const; void set(const script_value& key, const script_value& value) const; @@ -50,6 +57,9 @@ namespace ui_scripting table& operator=(const table& other); table& operator=(table&& other) noexcept; + bool operator==(const table& other); + bool operator!=(const table& other); + script_value get(const script_value& key) const; void set(const script_value& key, const script_value& value) const; @@ -75,6 +85,9 @@ namespace ui_scripting function& operator=(const function& other); function& operator=(function&& other) noexcept; + bool operator==(const function& other); + bool operator!=(const function& other); + arguments call(const arguments& arguments) const; game::hks::cclosure* ptr; diff --git a/src/client/main.cpp b/src/client/main.cpp index 7329eb4c..500a09c4 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -146,6 +146,16 @@ void limit_parallel_dll_loading() RegCloseKey(key); } +// solution for other processes that may launch the mod +void apply_proper_directory() +{ + char module_path[MAX_PATH]; + GetModuleFileNameA(nullptr, module_path, MAX_PATH); + PathRemoveFileSpecA(module_path); + SetCurrentDirectoryA(module_path); + SetDllDirectoryA(module_path); +} + int main() { FARPROC entry_point; @@ -169,6 +179,7 @@ int main() try { + apply_proper_directory(); remove_crash_file(); if (!component_loader::post_start()) return 0;