Discord in game join requests

This commit is contained in:
Federico Cecchetto 2022-04-11 11:05:21 +02:00
parent 691df3f607
commit f65a942897
13 changed files with 514 additions and 62 deletions

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

View File

@ -7,12 +7,25 @@
#include "command.hpp"
#include "network.hpp"
#include "party.hpp"
#include "materials.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"
#include "discord.hpp"
namespace discord
{
namespace
@ -21,8 +34,6 @@ namespace discord
void update_discord()
{
Discord_RunCallbacks();
if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded())
{
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer";
@ -105,6 +116,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
@ -122,16 +178,19 @@ namespace discord
handlers.ready = ready;
handlers.errored = errored;
handlers.disconnected = errored;
handlers.joinGame = joinGame;
handlers.joinGame = join_game;
handlers.spectateGame = nullptr;
handlers.joinRequest = joinRequest;
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;
@ -153,11 +212,8 @@ namespace discord
static void ready(const DiscordUser* request)
{
ZeroMemory(&discord_presence, sizeof(discord_presence));
discord_presence.instance = 1;
console::info("Discord: Ready on %s (%s)\n", request->username, request->userId);
Discord_UpdatePresence(&discord_presence);
}
@ -166,25 +222,55 @@ namespace discord
console::error("Discord: Error (%i): %s\n", error_code, message);
}
static void joinGame(const char* joinSecret)
static void join_game(const char* join_secret)
{
console::info("Discord: Join game called with join secret: %s\n", joinSecret);
console::info("Discord: Join game called with join secret: %s\n", join_secret);
scheduler::once([joinSecret]()
std::string secret = join_secret;
scheduler::once([=]()
{
game::netadr_s target{};
if (game::NET_StringToAdr(joinSecret, &target))
if (game::NET_StringToAdr(secret.data(), &target))
{
console::info("Discord: Connecting to server: %s\n", joinSecret);
console::info("Discord: Connecting to server: %s\n", secret.data());
party::connect(target);
}
}, scheduler::pipeline::main);
}
static void joinRequest(const DiscordUser* request)
static void join_request(const DiscordUser* request)
{
console::info("Discord: joinRequest from %s (%s)\n", request->username, request->userId);
// Discord_Respond(request->userId, DISCORD_REPLY_YES);
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);
}
}
};
}

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

View File

@ -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",
{

View File

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

View File

@ -3,5 +3,6 @@
namespace materials
{
void add(const std::string& name, const std::string& data);
bool exists(const std::string& name);
void clear();
}

View File

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

View File

@ -16,6 +16,9 @@ namespace scheduler
// The game's main thread
main,
// LUI context
lui,
count,
};

View File

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

View File

@ -9,4 +9,6 @@ namespace ui_scripting
void enable_error_hook();
void disable_error_hook();
bool lui_running();
}

View File

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

View File

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

View File

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