commit
e09f66c4c8
272
data/ui_scripts/discord/__init__.lua
Normal file
272
data/ui_scripts/discord/__init__.lua
Normal file
@ -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)
|
@ -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 <utils/string.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/http.hpp>
|
||||
|
||||
#include <discord_rpc.h>
|
||||
|
||||
#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<int>(server_net_info.ip[0]),
|
||||
static_cast<int>(server_net_info.ip[1]),
|
||||
static_cast<int>(server_net_info.ip[2]),
|
||||
static_cast<int>(server_net_info.ip[3]),
|
||||
static_cast<int>(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<int*>(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<std::chrono::seconds>(
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
7
src/client/component/discord.hpp
Normal file
7
src/client/component/discord.hpp
Normal file
@ -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);
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game_console.hpp"
|
||||
#include "ui_scripting.hpp"
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
@ -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",
|
||||
{
|
||||
|
@ -166,6 +166,14 @@ namespace materials
|
||||
});
|
||||
}
|
||||
|
||||
bool exists(const std::string& name)
|
||||
{
|
||||
return material_data.access<bool>([&](material_data_t& data_)
|
||||
{
|
||||
return data_.images.find(name) != data_.images.end();
|
||||
});
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
material_data.access([&](material_data_t& data_)
|
||||
|
@ -3,5 +3,6 @@
|
||||
namespace materials
|
||||
{
|
||||
void add(const std::string& name, const std::string& data);
|
||||
bool exists(const std::string& name);
|
||||
void clear();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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<void>();
|
||||
execute(pipeline::main);
|
||||
}
|
||||
|
||||
void hks_frame_stub()
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
if (state)
|
||||
{
|
||||
execute(pipeline::lui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& 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
|
||||
|
@ -16,6 +16,9 @@ namespace scheduler
|
||||
// The game's main thread
|
||||
main,
|
||||
|
||||
// LUI context
|
||||
lui,
|
||||
|
||||
count,
|
||||
};
|
||||
|
||||
|
@ -93,6 +93,7 @@ namespace ui_scripting
|
||||
|
||||
void hks_shutdown_stub()
|
||||
{
|
||||
converted_functions.clear();
|
||||
ui_scripting::lua::engine::stop();
|
||||
hks_shutdown_hook.invoke<void*>();
|
||||
}
|
||||
@ -107,15 +108,6 @@ namespace ui_scripting
|
||||
|
||||
return hks_allocator_hook.invoke<void*>(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<int>(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);
|
||||
|
||||
|
@ -9,4 +9,6 @@ namespace ui_scripting
|
||||
|
||||
void enable_error_hook();
|
||||
void disable_error_hook();
|
||||
|
||||
bool lui_running();
|
||||
}
|
@ -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 <discord_rpc.h>
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/io.hpp>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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{};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user