95% working discord RPC (disabled)
This commit is contained in:
parent
f8fc0e5dba
commit
bd3af45378
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -44,3 +44,6 @@
|
|||||||
[submodule "deps/curl"]
|
[submodule "deps/curl"]
|
||||||
path = deps/curl
|
path = deps/curl
|
||||||
url = https://github.com/curl/curl.git
|
url = https://github.com/curl/curl.git
|
||||||
|
[submodule "deps/discord-rpc"]
|
||||||
|
path = deps/discord-rpc
|
||||||
|
url = https://github.com/fedddddd/discord-rpc
|
||||||
|
1
deps/discord-rpc
vendored
Submodule
1
deps/discord-rpc
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit b3383798b353c31ea6770fee673740c27f6e3489
|
39
deps/premake/discord-rpc.lua
vendored
Normal file
39
deps/premake/discord-rpc.lua
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
discordrpc = {
|
||||||
|
source = path.join(dependencies.basePath, "discord-rpc"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function discordrpc.import()
|
||||||
|
links { "discord-rpc" }
|
||||||
|
discordrpc.includes()
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordrpc.includes()
|
||||||
|
includedirs {
|
||||||
|
path.join(discordrpc.source, "include"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function discordrpc.project()
|
||||||
|
project "discord-rpc"
|
||||||
|
language "C++"
|
||||||
|
|
||||||
|
discordrpc.includes()
|
||||||
|
rapidjson.import();
|
||||||
|
|
||||||
|
files {
|
||||||
|
path.join(discordrpc.source, "src/*.h"),
|
||||||
|
path.join(discordrpc.source, "src/*.cpp"),
|
||||||
|
}
|
||||||
|
|
||||||
|
removefiles {
|
||||||
|
path.join(discordrpc.source, "src/dllmain.cpp"),
|
||||||
|
path.join(discordrpc.source, "src/*_linux.cpp"),
|
||||||
|
path.join(discordrpc.source, "src/*_unix.cpp"),
|
||||||
|
path.join(discordrpc.source, "src/*_osx.cpp"),
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings "Off"
|
||||||
|
kind "StaticLib"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(dependencies, discordrpc)
|
@ -341,14 +341,14 @@ namespace terminal
|
|||||||
public:
|
public:
|
||||||
component()
|
component()
|
||||||
{
|
{
|
||||||
|
printf_hook.create(printf, printf_stub);
|
||||||
|
|
||||||
if (!game::environment::is_dedi())
|
if (!game::environment::is_dedi())
|
||||||
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
printf_hook.create(printf, printf_stub);
|
|
||||||
|
|
||||||
ShowWindow(GetConsoleWindow(), SW_SHOW);
|
ShowWindow(GetConsoleWindow(), SW_SHOW);
|
||||||
SetConsoleTitle("IW7-Mod: " VERSION);
|
SetConsoleTitle("IW7-Mod: " VERSION);
|
||||||
|
|
||||||
|
496
src/client/component/discord.cpp
Normal file
496
src/client/component/discord.cpp
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console/console.hpp"
|
||||||
|
#include "command.hpp"
|
||||||
|
#include "discord.hpp"
|
||||||
|
#include "party.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
|
#include "game/game.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
|
||||||
|
{
|
||||||
|
struct discord_presence_state_t
|
||||||
|
{
|
||||||
|
int start_timestamp;
|
||||||
|
int party_size;
|
||||||
|
int party_max;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct discord_presence_strings_t
|
||||||
|
{
|
||||||
|
std::string state;
|
||||||
|
std::string details;
|
||||||
|
std::string small_image_key;
|
||||||
|
std::string small_image_text;
|
||||||
|
std::string large_image_key;
|
||||||
|
std::string large_image_text;
|
||||||
|
std::string party_id;
|
||||||
|
std::string join_secret;
|
||||||
|
};
|
||||||
|
|
||||||
|
DiscordRichPresence discord_presence{};
|
||||||
|
discord_presence_strings_t discord_strings;
|
||||||
|
|
||||||
|
std::mutex avatar_map_mutex;
|
||||||
|
std::unordered_map<std::string, game::Material*> avatar_material_map;
|
||||||
|
game::Material* default_avatar_material{};
|
||||||
|
|
||||||
|
const char* get_large_image_name()
|
||||||
|
{
|
||||||
|
const auto mode = game::Com_GameMode_GetActiveGameMode();
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case game::GAME_MODE_SP:
|
||||||
|
return "menu_singleplayer";
|
||||||
|
case game::GAME_MODE_CP:
|
||||||
|
return "menu_zombies";
|
||||||
|
case game::GAME_MODE_MP:
|
||||||
|
default:
|
||||||
|
return "menu_multiplayer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_discord_frontend()
|
||||||
|
{
|
||||||
|
discord_presence.details = game::G_GAME_MODE_STRINGS_FORMATTED[game::Com_GameMode_GetActiveGameMode()];
|
||||||
|
discord_presence.startTimestamp = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
static const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange");
|
||||||
|
if (in_firing_range != nullptr && in_firing_range->current.enabled == 1)
|
||||||
|
{
|
||||||
|
discord_presence.state = "Firing Range";
|
||||||
|
discord_presence.largeImageKey = "mp_vlobby_room";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*/
|
||||||
|
discord_presence.state = "Main Menu";
|
||||||
|
discord_presence.largeImageKey = get_large_image_name();
|
||||||
|
//}
|
||||||
|
|
||||||
|
Discord_UpdatePresence(&discord_presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_discord_ingame()
|
||||||
|
{
|
||||||
|
static const auto mapname_dvar = game::Dvar_FindVar("mapname");
|
||||||
|
auto mapname = mapname_dvar->current.string;
|
||||||
|
|
||||||
|
discord_strings.large_image_key = mapname;
|
||||||
|
|
||||||
|
const auto mode = game::Com_GameMode_GetActiveGameMode();
|
||||||
|
const auto presence_key = utils::string::va(
|
||||||
|
"PRESENCE_%s%s",
|
||||||
|
(mode == game::GAME_MODE_SP ? "SP_" : (mode == game::GAME_MODE_CP ? "CP_" : "MP_")),
|
||||||
|
mapname);
|
||||||
|
|
||||||
|
if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE_ENTRY, presence_key) &&
|
||||||
|
!game::DB_IsXAssetDefault(game::ASSET_TYPE_LOCALIZE_ENTRY, presence_key))
|
||||||
|
{
|
||||||
|
mapname = game::UI_SafeTranslateString(presence_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == game::GAME_MODE_CP || mode == game::GAME_MODE_MP)
|
||||||
|
{
|
||||||
|
static const auto gametype_dvar = game::Dvar_FindVar("g_gametype");
|
||||||
|
static const auto max_clients_dvar = game::Dvar_FindVar("sv_maxclients");
|
||||||
|
static const auto hostname_dvar = game::Dvar_FindVar("sv_hostname");
|
||||||
|
|
||||||
|
const auto gametype_display_name = game::UI_GetGameTypeDisplayName(gametype_dvar->current.string);
|
||||||
|
const auto gametype = utils::string::strip(gametype_display_name);
|
||||||
|
|
||||||
|
discord_strings.details = std::format("{} on {}", gametype, mapname);
|
||||||
|
|
||||||
|
// cant find anything for numClients rn in iw7 :p
|
||||||
|
/*
|
||||||
|
const auto client_state = *game::client_state;
|
||||||
|
if (client_state != nullptr)
|
||||||
|
{
|
||||||
|
discord_presence.partySize = client_state->num_players;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
discord_presence.partySize = 1;
|
||||||
|
|
||||||
|
if (game::SV_Loaded() && !game::Com_FrontEnd_IsInFrontEnd())
|
||||||
|
{
|
||||||
|
discord_strings.state = "Private Match";
|
||||||
|
discord_presence.partyMax = max_clients_dvar->current.integer;
|
||||||
|
discord_presence.partyPrivacy = DISCORD_PARTY_PRIVATE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
discord_strings.state = utils::string::strip(hostname_dvar->current.string);
|
||||||
|
|
||||||
|
const auto server_connection_state = party::get_server_connection_state();
|
||||||
|
const auto server_ip_port = std::format("{}.{}.{}.{}:{}",
|
||||||
|
static_cast<int>(server_connection_state.host.ip[0]),
|
||||||
|
static_cast<int>(server_connection_state.host.ip[1]),
|
||||||
|
static_cast<int>(server_connection_state.host.ip[2]),
|
||||||
|
static_cast<int>(server_connection_state.host.ip[3]),
|
||||||
|
static_cast<int>(ntohs(server_connection_state.host.port))
|
||||||
|
);
|
||||||
|
|
||||||
|
discord_strings.party_id = utils::cryptography::sha1::compute(server_ip_port, true).substr(0, 8);
|
||||||
|
discord_presence.partyMax = server_connection_state.max_clients;
|
||||||
|
discord_presence.partyPrivacy = DISCORD_PARTY_PUBLIC;
|
||||||
|
discord_strings.join_secret = server_ip_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto server_discord_info = party::get_server_discord_info();
|
||||||
|
if (server_discord_info.has_value())
|
||||||
|
{
|
||||||
|
discord_strings.small_image_key = server_discord_info->image;
|
||||||
|
discord_strings.small_image_text = server_discord_info->image_text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == game::GAME_MODE_SP)
|
||||||
|
{
|
||||||
|
discord_strings.details = mapname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord_presence.startTimestamp == 0)
|
||||||
|
{
|
||||||
|
discord_presence.startTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
discord_presence.state = discord_strings.state.data();
|
||||||
|
discord_presence.details = discord_strings.details.data();
|
||||||
|
discord_presence.smallImageKey = discord_strings.small_image_key.data();
|
||||||
|
discord_presence.smallImageText = discord_strings.small_image_text.data();
|
||||||
|
discord_presence.largeImageKey = discord_strings.large_image_key.data();
|
||||||
|
discord_presence.largeImageText = discord_strings.large_image_text.data();
|
||||||
|
discord_presence.partyId = discord_strings.party_id.data();
|
||||||
|
discord_presence.joinSecret = discord_strings.join_secret.data();
|
||||||
|
|
||||||
|
Discord_UpdatePresence(&discord_presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_discord()
|
||||||
|
{
|
||||||
|
const auto saved_time = discord_presence.startTimestamp;
|
||||||
|
discord_presence = {};
|
||||||
|
discord_presence.startTimestamp = saved_time;
|
||||||
|
|
||||||
|
//const auto game_initialized = game::client_actives_something[0] == 1; // 196 * client num = resultc
|
||||||
|
const auto game_initialized = false; // TODO: figure out a way to do this
|
||||||
|
if (!game_initialized || game::Com_FrontEndScene_IsActive())
|
||||||
|
{
|
||||||
|
update_discord_frontend();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
update_discord_ingame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
game::Material* create_avatar_material(const std::string& name, const std::string& data)
|
||||||
|
{
|
||||||
|
const auto material = materials::create_material(name);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!materials::setup_material_image(material, data))
|
||||||
|
{
|
||||||
|
materials::free_material(material);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard _0(avatar_map_mutex);
|
||||||
|
avatar_material_map.insert(std::make_pair(name, material));
|
||||||
|
}
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
materials::free_material(material);
|
||||||
|
console::error("Failed to load user avatar image: %s\n", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& value = data.value();
|
||||||
|
if (value.code != CURLE_OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto name = utils::string::va(AVATAR, id.data());
|
||||||
|
create_avatar_material(name, value.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void download_default_avatar()
|
||||||
|
{
|
||||||
|
const auto data = utils::http::get_data(DEFAULT_AVATAR_URL);
|
||||||
|
if (!data.has_value())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& value = data.value();
|
||||||
|
if (value.code != CURLE_OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
default_avatar_material = create_avatar_material(DEFAULT_AVATAR, value.buffer);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ready(const DiscordUser* request)
|
||||||
|
{
|
||||||
|
DiscordRichPresence presence{};
|
||||||
|
presence.instance = 1;
|
||||||
|
presence.state = "";
|
||||||
|
console::info("Discord: Ready on %s (%s)\n", request->username, request->userId);
|
||||||
|
Discord_UpdatePresence(&presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void errored(const int error_code, const char* message)
|
||||||
|
{
|
||||||
|
console::error("Discord: %s (%i)\n", message, error_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void join_game(const char* join_secret)
|
||||||
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
console::debug("Discord: join_game called with secret '%s'\n", join_secret);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
scheduler::once([=]
|
||||||
|
{
|
||||||
|
game::netadr_s target{};
|
||||||
|
if (game::NET_StringToAdr(join_secret, &target))
|
||||||
|
{
|
||||||
|
console::info("Discord: Connecting to server '%s'\n", join_secret);
|
||||||
|
party::connect(target);
|
||||||
|
}
|
||||||
|
}, scheduler::pipeline::main);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
std::string get_display_name(const DiscordUser* user)
|
||||||
|
{
|
||||||
|
if (user->discriminator != nullptr && user->discriminator != "0"s)
|
||||||
|
{
|
||||||
|
return std::format("{}#{}", user->username, user->discriminator);
|
||||||
|
}
|
||||||
|
else if (user->globalName[0] != 0)
|
||||||
|
{
|
||||||
|
return user->globalName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return user->username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void join_request(const DiscordUser* request)
|
||||||
|
{
|
||||||
|
console::debug("Discord: Join request from %s (%s)\n", request->username, request->userId);
|
||||||
|
Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (game::Com_FrontEnd_IsInFrontEnd() || !ui_scripting::lui_running())
|
||||||
|
{
|
||||||
|
Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::unordered_map<std::string, std::chrono::high_resolution_clock::time_point> last_requests;
|
||||||
|
|
||||||
|
const std::string user_id = request->userId;
|
||||||
|
const std::string avatar = request->avatar;
|
||||||
|
const std::string discriminator = request->discriminator;
|
||||||
|
const std::string username = request->username;
|
||||||
|
const auto display_name = get_display_name(request);
|
||||||
|
|
||||||
|
const auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
auto iter = last_requests.find(user_id);
|
||||||
|
if (iter != last_requests.end())
|
||||||
|
{
|
||||||
|
if ((now - iter->second) < 15s)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iter->second = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
last_requests.insert(std::make_pair(user_id, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: lui work to support join requests (check h1-mod)
|
||||||
|
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);
|
||||||
|
request_table.set("displayname", display_name);
|
||||||
|
|
||||||
|
ui_scripting::notify("discord_join_request",
|
||||||
|
{
|
||||||
|
{"request", request_table}
|
||||||
|
});
|
||||||
|
}, scheduler::pipeline::lui);
|
||||||
|
|
||||||
|
const auto material_name = utils::string::va(AVATAR, user_id.data());
|
||||||
|
if (!avatar.empty() && !avatar_material_map.contains(material_name))
|
||||||
|
{
|
||||||
|
download_user_avatar(user_id, avatar);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
/*
|
||||||
|
void set_default_bindings()
|
||||||
|
{
|
||||||
|
const auto set_binding = [](const std::string& command, const game::keyNum_t key)
|
||||||
|
{
|
||||||
|
const auto binding = game::Key_GetBindingForCmd(command.data());
|
||||||
|
for (auto i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
if (game::playerKeys[0].keys[i].binding == binding)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game::playerKeys[0].keys[key].binding == 0)
|
||||||
|
{
|
||||||
|
game::Key_SetBinding(0, key, binding);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
set_binding("discord_accept", game::K_F1);
|
||||||
|
set_binding("discord_deny", game::K_F2);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
game::Material* get_avatar_material(const std::string& id)
|
||||||
|
{
|
||||||
|
const auto material_name = utils::string::va(AVATAR, id.data());
|
||||||
|
const auto iter = avatar_material_map.find(material_name);
|
||||||
|
if (iter == avatar_material_map.end())
|
||||||
|
{
|
||||||
|
return default_avatar_material;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void respond(const std::string& id, int reply)
|
||||||
|
{
|
||||||
|
scheduler::once([=]()
|
||||||
|
{
|
||||||
|
Discord_Respond(id.data(), reply);
|
||||||
|
}, scheduler::pipeline::async);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
if (game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscordEventHandlers handlers{};
|
||||||
|
handlers.ready = ready;
|
||||||
|
handlers.errored = errored;
|
||||||
|
handlers.disconnected = errored;
|
||||||
|
handlers.spectateGame = nullptr;
|
||||||
|
handlers.joinGame = join_game;
|
||||||
|
handlers.joinRequest = join_request; // not fully supported yet
|
||||||
|
|
||||||
|
Discord_Initialize("1215500480873103400", &handlers, 1, nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
if (game::environment::is_mp())
|
||||||
|
{
|
||||||
|
scheduler::on_game_initialized([]
|
||||||
|
{
|
||||||
|
scheduler::once(download_default_avatar, scheduler::async);
|
||||||
|
set_default_bindings();
|
||||||
|
}, scheduler::main);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
scheduler::loop(Discord_RunCallbacks, scheduler::async, 500ms);
|
||||||
|
scheduler::loop(update_discord, scheduler::async, 5s);
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
command::add("discord_accept", []()
|
||||||
|
{
|
||||||
|
ui_scripting::notify("discord_response", {{"accept", true}});
|
||||||
|
});
|
||||||
|
|
||||||
|
command::add("discord_deny", []()
|
||||||
|
{
|
||||||
|
ui_scripting::notify("discord_response", {{"accept", false}});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void pre_destroy() override
|
||||||
|
{
|
||||||
|
if (!initialized_ || game::environment::is_dedi())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Discord_Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//REGISTER_COMPONENT(discord::component)
|
9
src/client/component/discord.hpp
Normal file
9
src/client/component/discord.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//#include "game/game.hpp"
|
||||||
|
|
||||||
|
namespace discord
|
||||||
|
{
|
||||||
|
//game::Material* get_avatar_material(const std::string& id);
|
||||||
|
//void respond(const std::string& id, int reply);
|
||||||
|
}
|
@ -24,6 +24,32 @@ namespace party
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
connection_state server_connection_state{};
|
connection_state server_connection_state{};
|
||||||
|
std::optional<discord_information> server_discord_info{};
|
||||||
|
|
||||||
|
/*
|
||||||
|
struct usermap_file
|
||||||
|
{
|
||||||
|
std::string extension;
|
||||||
|
std::string name;
|
||||||
|
bool optional;
|
||||||
|
};
|
||||||
|
|
||||||
|
// snake case these names before release
|
||||||
|
std::vector<usermap_file> usermap_files =
|
||||||
|
{
|
||||||
|
{".ff", "usermap_hash", false},
|
||||||
|
{"_load.ff", "usermap_load_hash", true},
|
||||||
|
{".arena", "usermap_arena_hash", true},
|
||||||
|
{".pak", "usermap_pak_hash", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<usermap_file> mod_files =
|
||||||
|
{
|
||||||
|
{".ff", "mod_hash", false},
|
||||||
|
{"_pre_gfx.ff", "mod_pre_gfx_hash", true},
|
||||||
|
{".pak", "mod_pak_hash", true},
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
bool preloaded_map = false;
|
bool preloaded_map = false;
|
||||||
|
|
||||||
@ -38,8 +64,8 @@ namespace party
|
|||||||
|
|
||||||
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype, int sv_maxclients)
|
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype, int sv_maxclients)
|
||||||
{
|
{
|
||||||
if (game::Com_GameMode_GetActiveGameMode() != game::GAME_MODE_MP &&
|
const auto mode = game::Com_GameMode_GetActiveGameMode();
|
||||||
game::Com_GameMode_GetActiveGameMode() != game::GAME_MODE_CP)
|
if (mode != game::GAME_MODE_MP && mode != game::GAME_MODE_CP)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -341,6 +367,11 @@ namespace party
|
|||||||
return server_connection_state;
|
return server_connection_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<discord_information> get_server_discord_info()
|
||||||
|
{
|
||||||
|
return server_discord_info;
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -499,6 +530,34 @@ namespace party
|
|||||||
info.set("playmode", utils::string::va("%i", game::Com_GameMode_GetActiveGameMode()));
|
info.set("playmode", utils::string::va("%i", game::Com_GameMode_GetActiveGameMode()));
|
||||||
info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running") && !game::Com_FrontEndScene_IsActive()));
|
info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running") && !game::Com_FrontEndScene_IsActive()));
|
||||||
info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
||||||
|
info.set("sv_wwwBaseUrl", get_dvar_string("sv_wwwBaseUrl"));
|
||||||
|
info.set("sv_discordImageUrl", get_dvar_string("sv_discordImageUrl"));
|
||||||
|
info.set("sv_discordImageText", get_dvar_string("sv_discordImageText"));
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (!fastfiles::is_stock_map(mapname))
|
||||||
|
{
|
||||||
|
for (const auto& file : usermap_files)
|
||||||
|
{
|
||||||
|
const auto path = get_usermap_file_path(mapname, file.extension);
|
||||||
|
const auto hash = get_file_hash(path);
|
||||||
|
info.set(file.name, hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto fs_game = get_dvar_string("fs_game");
|
||||||
|
info.set("fs_game", fs_game);
|
||||||
|
|
||||||
|
if (!fs_game.empty())
|
||||||
|
{
|
||||||
|
for (const auto& file : mod_files)
|
||||||
|
{
|
||||||
|
const auto hash = get_file_hash(utils::string::va("%s/mod%s",
|
||||||
|
fs_game.data(), file.extension.data()));
|
||||||
|
info.set(file.name, hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
network::send(target, "infoResponse", info.build(), '\n');
|
network::send(target, "infoResponse", info.build(), '\n');
|
||||||
});
|
});
|
||||||
@ -518,6 +577,13 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto protocol = info.get("protocol");
|
||||||
|
if (std::atoi(protocol.data()) != PROTOCOL)
|
||||||
|
{
|
||||||
|
info_response_error("Connection failed: Invalid protocol.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (info.get("challenge") != server_connection_state.challenge)
|
if (info.get("challenge") != server_connection_state.challenge)
|
||||||
{
|
{
|
||||||
info_response_error("Connection failed: Invalid challenge.");
|
info_response_error("Connection failed: Invalid challenge.");
|
||||||
@ -567,6 +633,14 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server_connection_state.base_url = info.get("sv_wwwBaseUrl");
|
||||||
|
/*
|
||||||
|
if (download_files(target, info, false))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
server_connection_state.motd = info.get("sv_motd");
|
server_connection_state.motd = info.get("sv_motd");
|
||||||
server_connection_state.max_clients = std::stoi(sv_maxclients_str);
|
server_connection_state.max_clients = std::stoi(sv_maxclients_str);
|
||||||
|
|
||||||
@ -579,6 +653,14 @@ namespace party
|
|||||||
const auto profile_info_value = profile_info.value_or(profile_infos::profile_info{});
|
const auto profile_info_value = profile_info.value_or(profile_infos::profile_info{});
|
||||||
profile_infos::send_profile_info(target, steam::SteamUser()->GetSteamID().bits, profile_info_value);
|
profile_infos::send_profile_info(target, steam::SteamUser()->GetSteamID().bits, profile_info_value);
|
||||||
|
|
||||||
|
discord_information discord_info{};
|
||||||
|
discord_info.image = info.get("sv_discordImageUrl");
|
||||||
|
discord_info.image_text = info.get("sv_discordImageText");
|
||||||
|
if (!discord_info.image.empty() || !discord_info.image_text.empty())
|
||||||
|
{
|
||||||
|
server_discord_info.emplace(discord_info);
|
||||||
|
}
|
||||||
|
|
||||||
connect_to_party(target, mapname, gametype, sv_maxclients);
|
connect_to_party(target, mapname, gametype, sv_maxclients);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,9 @@ namespace party
|
|||||||
void connect(const game::netadr_s& target);
|
void connect(const game::netadr_s& target);
|
||||||
void start_map(const std::string& mapname, bool dev = false);
|
void start_map(const std::string& mapname, bool dev = false);
|
||||||
|
|
||||||
//void clear_sv_motd();
|
void clear_sv_motd();
|
||||||
connection_state get_server_connection_state();
|
connection_state get_server_connection_state();
|
||||||
//std::optional<discord_information> get_server_discord_info();
|
std::optional<discord_information> get_server_discord_info();
|
||||||
|
|
||||||
int get_client_num_by_name(const std::string& name);
|
int get_client_num_by_name(const std::string& name);
|
||||||
|
|
||||||
|
@ -85,6 +85,14 @@ namespace game
|
|||||||
"cp",
|
"cp",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char* G_GAME_MODE_STRINGS_FORMATTED[] =
|
||||||
|
{
|
||||||
|
"Multiplayer", // this is really none, but its for discord presence :P
|
||||||
|
"Singleplayer",
|
||||||
|
"Multiplayer",
|
||||||
|
"Zombies",
|
||||||
|
};
|
||||||
|
|
||||||
const char* Com_GameMode_GetActiveGameModeStr()
|
const char* Com_GameMode_GetActiveGameModeStr()
|
||||||
{
|
{
|
||||||
return G_GAME_MODE_STRINGS[game::Com_GameMode_GetActiveGameMode()];
|
return G_GAME_MODE_STRINGS[game::Com_GameMode_GetActiveGameMode()];
|
||||||
|
@ -54,6 +54,7 @@ namespace game
|
|||||||
int SV_Cmd_Argc();
|
int SV_Cmd_Argc();
|
||||||
const char* SV_Cmd_Argv(int index);
|
const char* SV_Cmd_Argv(int index);
|
||||||
|
|
||||||
|
extern const char* G_GAME_MODE_STRINGS_FORMATTED[];
|
||||||
const char* Com_GameMode_GetActiveGameModeStr();
|
const char* Com_GameMode_GetActiveGameModeStr();
|
||||||
const char* Com_GameMode_GetGameModeStr(GameModeType gameMode);
|
const char* Com_GameMode_GetGameModeStr(GameModeType gameMode);
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ namespace game
|
|||||||
WEAK symbol<void()> Com_FrontEndScene_ShutdownAndDisable{ 0x5AEFB0 };
|
WEAK symbol<void()> Com_FrontEndScene_ShutdownAndDisable{ 0x5AEFB0 };
|
||||||
WEAK symbol<void()> Com_FrontEndScene_Shutdown{ 0x5AED00 };
|
WEAK symbol<void()> Com_FrontEndScene_Shutdown{ 0x5AED00 };
|
||||||
|
|
||||||
|
WEAK symbol<int> s_frontEndScene_state{ 0x4BFF608 };
|
||||||
|
|
||||||
WEAK symbol<void(GameModeType)> Com_GameMode_SetDesiredGameMode{ 0x5AFDA0 };
|
WEAK symbol<void(GameModeType)> Com_GameMode_SetDesiredGameMode{ 0x5AFDA0 };
|
||||||
WEAK symbol<GameModeType()> Com_GameMode_GetActiveGameMode{ 0x5AFD50 };
|
WEAK symbol<GameModeType()> Com_GameMode_GetActiveGameMode{ 0x5AFD50 };
|
||||||
WEAK symbol<bool(const char* name)> Com_GameMode_SupportsMap{ 0x5AFE10 };
|
WEAK symbol<bool(const char* name)> Com_GameMode_SupportsMap{ 0x5AFE10 };
|
||||||
@ -258,6 +260,7 @@ namespace game
|
|||||||
WEAK symbol<const char* (const char*)> UI_GetMapDisplayName{ 0xCC6270 };
|
WEAK symbol<const char* (const char*)> UI_GetMapDisplayName{ 0xCC6270 };
|
||||||
WEAK symbol<const char* (const char*)> UI_GetGameTypeDisplayName{ 0xCC61C0 };
|
WEAK symbol<const char* (const char*)> UI_GetGameTypeDisplayName{ 0xCC61C0 };
|
||||||
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{ 0xCC9710 };
|
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{ 0xCC9710 };
|
||||||
|
WEAK symbol<const char* (const char* string)> UI_SafeTranslateString{ 0xCC9790 };
|
||||||
|
|
||||||
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x12C0758 };
|
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x12C0758 };
|
||||||
WEAK symbol<int(jmp_buf* Buf)> _setjmp{ 0x1423110 };
|
WEAK symbol<int(jmp_buf* Buf)> _setjmp{ 0x1423110 };
|
||||||
@ -266,6 +269,8 @@ namespace game
|
|||||||
* Variables
|
* Variables
|
||||||
**************************************************************/
|
**************************************************************/
|
||||||
|
|
||||||
|
//WEAK symbol<int> client_actives_something{ 0x2246C51 }; // 0x140995A8B in IW7 idb shows this to be cgameinitialized times the local client number
|
||||||
|
|
||||||
WEAK symbol<int> g_script_error_level{ 0x6B16298 };
|
WEAK symbol<int> g_script_error_level{ 0x6B16298 };
|
||||||
WEAK symbol<jmp_buf> g_script_error{ 0x6B162A0 };
|
WEAK symbol<jmp_buf> g_script_error{ 0x6B162A0 };
|
||||||
|
|
||||||
|
@ -136,6 +136,14 @@ namespace utils::string
|
|||||||
*out = '\0';
|
*out = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string strip(const std::string& string)
|
||||||
|
{
|
||||||
|
std::string new_string;
|
||||||
|
new_string.resize(string.size() + 1, 0);
|
||||||
|
strip(string.data(), new_string.data(), static_cast<int>(new_string.size()));
|
||||||
|
return new_string.data();
|
||||||
|
}
|
||||||
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable: 4100)
|
#pragma warning(disable: 4100)
|
||||||
std::string convert(const std::wstring& wstr)
|
std::string convert(const std::wstring& wstr)
|
||||||
|
@ -93,6 +93,7 @@ namespace utils::string
|
|||||||
std::string get_clipboard_data();
|
std::string get_clipboard_data();
|
||||||
|
|
||||||
void strip(const char* in, char* out, int max);
|
void strip(const char* in, char* out, int max);
|
||||||
|
std::string strip(const std::string& string);
|
||||||
|
|
||||||
std::string convert(const std::wstring& wstr);
|
std::string convert(const std::wstring& wstr);
|
||||||
std::wstring convert(const std::string& str);
|
std::wstring convert(const std::string& str);
|
||||||
|
Loading…
Reference in New Issue
Block a user