diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp index 8b50ff36..9c2299a6 100644 --- a/src/client/component/discord.cpp +++ b/src/client/component/discord.cpp @@ -31,142 +31,133 @@ namespace discord { DiscordRichPresence discord_presence; - void update_discord() + void update_discord_frontend() { - if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded()) + discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer"); + discord_presence.startTimestamp = 0; + + const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange"); + if (in_firing_range && in_firing_range->current.enabled == 1) { - discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer"); - discord_presence.state = "Main Menu"; - - discord_presence.partySize = 0; - discord_presence.partyMax = 0; - discord_presence.startTimestamp = 0; - discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer"); - discord_presence.largeImageText = ""; - discord_presence.smallImageKey = ""; - - discord_presence.matchSecret = ""; - discord_presence.joinSecret = ""; - discord_presence.partyId = ""; - - const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange"); - if (in_firing_range && in_firing_range->current.enabled == 1) - { - discord_presence.state = "Firing Range"; - discord_presence.largeImageKey = "mp_vlobby_room"; - } + discord_presence.state = "Firing Range"; + discord_presence.largeImageKey = "mp_vlobby_room"; } else { - static char details[0x80] = {0}; - const auto mapname = game::Dvar_FindVar("mapname")->current.string; - const auto presence_key = utils::string::va("PRESENCE_%s%s", SELECT_VALUE("SP_", ""), mapname); - const char* clean_mapname = mapname; - - if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE, presence_key) && !game::DB_IsXAssetDefault(game::ASSET_TYPE_LOCALIZE, presence_key)) - { - clean_mapname = game::UI_SafeTranslateString(presence_key); - } - - if (game::environment::is_mp()) - { - static char clean_gametype[0x80] = {0}; - const auto gametype = game::UI_GetGameTypeDisplayName(game::Dvar_FindVar("g_gametype")->current.string); - utils::string::strip(gametype, clean_gametype, sizeof(clean_gametype)); - strcpy_s(details, 0x80, utils::string::va("%s on %s", clean_gametype, clean_mapname)); - - 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(); - - if (game::SV_Loaded()) - { - 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; - } - - const auto client_state = *game::mp::client_state; - if (client_state != nullptr) - { - discord_presence.partySize = client_state->num_players; - } - else - { - discord_presence.partySize = 0; - } - - discord_presence.partyMax = max_clients; - discord_presence.state = clean_hostname; - - auto discord_map_image_asset = mapname; - if (!fastfiles::is_stock_map(mapname)) - { - discord_map_image_asset = "menu_multiplayer"; // TODO: maybe add usermap images - } - - auto discord_server_info = party::get_discord_server_image(); - if (!discord_server_info.empty()) - { - // prioritize server image as large image instead - discord_presence.smallImageKey = discord_map_image_asset; - discord_presence.largeImageKey = discord_server_info.data(); - discord_server_info = party::get_discord_server_text(); - if (!discord_server_info.empty()) - { - discord_presence.largeImageText = discord_server_info.data(); - } - } - else - { - discord_presence.smallImageKey = ""; - discord_presence.largeImageKey = discord_map_image_asset; - discord_presence.largeImageText = ""; - } - } - else if (game::environment::is_sp()) - { - discord_presence.state = ""; - discord_presence.largeImageKey = mapname; - discord_presence.smallImageKey = ""; - strcpy_s(details, 0x80, clean_mapname); - } - - discord_presence.details = details; - - if (!discord_presence.startTimestamp) - { - discord_presence.startTimestamp = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - } + discord_presence.state = "Main Menu"; + discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer"); } Discord_UpdatePresence(&discord_presence); } + void update_discord_ingame() + { + static const auto mapname_dvar = game::Dvar_FindVar("mapname"); + auto mapname = mapname_dvar->current.string; + + discord_presence.largeImageKey = mapname; + + const auto presence_key = utils::string::va("PRESENCE_%s%s", SELECT_VALUE("SP_", ""), mapname); + if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE, presence_key) && + !game::DB_IsXAssetDefault(game::ASSET_TYPE_LOCALIZE, presence_key)) + { + mapname = game::UI_SafeTranslateString(presence_key); + } + + if (game::environment::is_mp()) + { + // setup Discord details (Free-for-all on Shipment) + static char gametype[0x80] = {0}; + static const auto gametype_dvar = game::Dvar_FindVar("g_gametype"); + const auto gametype_display_name = game::UI_GetGameTypeDisplayName(gametype_dvar->current.string); + utils::string::strip(gametype_display_name, gametype, sizeof(gametype)); + + discord_presence.details = utils::string::va("%s on %s", gametype, mapname); + + // setup Discord party (1 of 18) + const auto client_state = *game::mp::client_state; + if (client_state != nullptr) + { + discord_presence.partySize = client_state->num_players; + } + + if (game::SV_Loaded()) + { + discord_presence.state = "Private Match"; + static const auto maxclients_dvar = game::Dvar_FindVar("sv_maxclients"); + discord_presence.partyMax = maxclients_dvar->current.integer; + discord_presence.partyPrivacy = DISCORD_PARTY_PRIVATE; + } + else + { + static char hostname[0x80] = {0}; + static const auto hostname_dvar = game::Dvar_FindVar("sv_hostname"); + utils::string::strip(hostname_dvar->current.string, hostname, sizeof(hostname)); + discord_presence.state = hostname; + + const auto server_connection_state = party::get_server_connection_state(); + + const auto server_ip_port = utils::string::va("%i.%i.%i.%i:%i", + static_cast(server_connection_state.host.ip[0]), + static_cast(server_connection_state.host.ip[1]), + static_cast(server_connection_state.host.ip[2]), + static_cast(server_connection_state.host.ip[3]), + static_cast(ntohs(server_connection_state.host.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.partyMax = server_connection_state.max_clients; + discord_presence.partyPrivacy = DISCORD_PARTY_PUBLIC; + + static char join_secret[0x80] = { 0 }; + strcpy_s(join_secret, 0x80, server_ip_port); + discord_presence.joinSecret = join_secret; + } + + auto server_discord_information = party::get_server_discord_information(); + if (!server_discord_information.image.empty()) + { + discord_presence.smallImageKey = server_discord_information.image.data(); + discord_presence.smallImageText = server_discord_information.image_text.data(); + } + } + else if (game::environment::is_sp()) + { + discord_presence.details = mapname; + } + + if (discord_presence.startTimestamp == 0) + { + discord_presence.startTimestamp = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + } + + Discord_UpdatePresence(&discord_presence); + } + + void update_discord() + { + Discord_RunCallbacks(); + + // reset presence data + const auto saved_time = discord_presence.startTimestamp; + discord_presence = {}; + discord_presence.startTimestamp = saved_time; + + if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded()) + { + update_discord_frontend(); + } + else + { + update_discord_ingame(); + } + } + void download_user_avatar(const std::string& id, const std::string& avatar) { const auto data = utils::http::get_data( @@ -203,116 +194,39 @@ namespace discord has_default_avatar = true; materials::add(DEFAULT_AVATAR, value.buffer); } - } - std::string get_avatar_material(const std::string& id) - { - const auto avatar_name = utils::string::va(AVATAR, id.data()); - if (materials::exists(avatar_name)) + void ready(const DiscordUser* request) { - 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 - { - public: - void post_load() override - { - if (game::environment::is_dedi()) - { - return; - } - - DiscordEventHandlers handlers; - ZeroMemory(&handlers, sizeof(handlers)); - handlers.ready = ready; - handlers.errored = errored; - handlers.disconnected = errored; - handlers.spectateGame = nullptr; - - if (game::environment::is_mp()) - { - handlers.joinGame = join_game; - handlers.joinRequest = join_request; - } - else - { - handlers.joinGame = nullptr; - handlers.joinRequest = nullptr; - } - - 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; - } - - void pre_destroy() override - { - if (!initialized_ || game::environment::is_dedi()) - { - return; - } - - Discord_Shutdown(); - } - - private: - bool initialized_ = false; - - static void ready(const DiscordUser* request) - { - ZeroMemory(&discord_presence, sizeof(discord_presence)); - discord_presence.instance = 1; + DiscordRichPresence presence{}; + presence.instance = 1; + presence.state = ""; console::info("Discord: Ready on %s (%s)\n", request->username, request->userId); - Discord_UpdatePresence(&discord_presence); + Discord_UpdatePresence(&presence); } - static void errored(const int error_code, const char* message) + void errored(const int error_code, const char* message) { - console::error("Discord: Error (%i): %s\n", error_code, message); + console::error("Discord: %s (%i)\n", message, error_code); } - static void join_game(const char* join_secret) + void join_game(const char* join_secret) { + console::debug("Discord: join_game called with secret '%s'\n", join_secret); + scheduler::once([=] { game::netadr_s target{}; if (game::NET_StringToAdr(join_secret, &target)) { - console::info("Discord: Connecting to server: %s\n", join_secret); + console::info("Discord: Connecting to server '%s'\n", join_secret); party::connect(target); } }, scheduler::pipeline::main); } - static void join_request(const DiscordUser* request) + void join_request(const DiscordUser* request) { - console::info("Discord: Join request from %s (%s)\n", request->username, request->userId); + console::debug("Discord: Join request from %s (%s)\n", request->username, request->userId); if (game::Com_InFrontend() || !ui_scripting::lui_running()) { @@ -344,6 +258,79 @@ namespace discord download_user_avatar(user_id, avatar); } } + } + + 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 + { + 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; + + if (game::environment::is_mp()) + { + handlers.joinGame = join_game; + handlers.joinRequest = join_request; + } + else + { + handlers.joinGame = nullptr; + handlers.joinRequest = nullptr; + } + + Discord_Initialize("947125042930667530", &handlers, 1, nullptr); + + scheduler::once(download_default_avatar, scheduler::pipeline::async); + scheduler::loop(update_discord, scheduler::pipeline::async, 5s); + + initialized_ = true; + } + + void pre_destroy() override + { + if (!initialized_ || game::environment::is_dedi()) + { + return; + } + + Discord_Shutdown(); + } + + private: + bool initialized_ = false; }; } diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index eefb6656..5aa10986 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -32,15 +32,8 @@ namespace party { namespace { - struct - { - game::netadr_s host{}; - std::string challenge{}; - bool hostDefined{false}; - } connect_state; - - std::string sv_motd; - int sv_maxclients; + connection_state server_connection_state{}; + discord_information server_discord_information{}; struct usermap_file { @@ -165,11 +158,11 @@ namespace party const char* get_didyouknow_stub(void* table, int row, int column) { - if (party::sv_motd.empty()) + if (server_connection_state.motd.empty()) { return utils::hook::invoke(0x5A0AC0_b, table, row, column); } - return utils::string::va("%s", party::sv_motd.data()); + return utils::string::va("%s", server_connection_state.motd.data()); } void disconnect() @@ -498,7 +491,7 @@ namespace party command::execute("disconnect"); scheduler::once([] { - connect(connect_state.host); + connect(server_connection_state.host); }, scheduler::pipeline::main); return; } @@ -614,7 +607,7 @@ namespace party void clear_sv_motd() { - party::sv_motd.clear(); + server_connection_state.motd.clear(); } int get_client_num_by_name(const std::string& name) @@ -636,9 +629,9 @@ namespace party return -1; } - void reset_connect_state() + void reset_server_connection_state() { - connect_state = {}; + server_connection_state = {}; } int get_client_count() @@ -691,16 +684,11 @@ namespace party command::execute("lui_open_popup popup_acceptinginvite", false); - connect_state.host = target; - connect_state.challenge = utils::cryptography::random::get_challenge(); - connect_state.hostDefined = true; + server_connection_state.host = target; + server_connection_state.challenge = utils::cryptography::random::get_challenge(); + server_connection_state.hostDefined = true; - network::send(target, "getInfo", connect_state.challenge); - } - - game::netadr_s get_state_host() - { - return connect_state.host; + network::send(target, "getInfo", server_connection_state.challenge); } void start_map(const std::string& mapname, bool dev) @@ -762,19 +750,14 @@ namespace party } } - int server_client_count() + connection_state get_server_connection_state() { - return party::sv_maxclients; + return server_connection_state; } - std::string get_discord_server_image() + discord_information get_server_discord_information() { - return saved_info_response.info_string.get("sv_discordImageUrl"); - } - - std::string get_discord_server_text() - { - return saved_info_response.info_string.get("sv_discordImageText"); + return server_discord_information; } class component final : public component_interface @@ -859,7 +842,7 @@ namespace party command::add("reconnect", [](const command::params& argument) { - if (!connect_state.hostDefined) + if (!server_connection_state.hostDefined) { console::info("Cannot connect to server.\n"); return; @@ -872,7 +855,7 @@ namespace party } else { - connect(connect_state.host); + connect(server_connection_state.host); } }); @@ -1104,7 +1087,7 @@ namespace party const utils::info_string info(data); server_list::handle_info_response(target, info); - if (connect_state.host != target) + if (server_connection_state.host != target) { return; } @@ -1120,7 +1103,7 @@ namespace party return; } - if (info.get("challenge") != connect_state.challenge) + if (info.get("challenge") != server_connection_state.challenge) { menu_error("Connection failed: Invalid challenge."); return; @@ -1166,8 +1149,11 @@ namespace party return; } - party::sv_motd = info.get("sv_motd"); - party::sv_maxclients = std::stoi(info.get("sv_maxclients")); + server_connection_state.motd = info.get("sv_motd"); + server_connection_state.max_clients = std::stoi(info.get("sv_maxclients")); + server_connection_state.base_url = info.get("sv_wwwBaseUrl"); + server_discord_information.image = info.get("sv_discordImageUrl"); + server_discord_information.image_text = info.get("sv_discordImageText"); connect_to_party(target, mapname, gametype); }); diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp index b5eeb1ec..6a367d66 100644 --- a/src/client/component/party.hpp +++ b/src/client/component/party.hpp @@ -3,21 +3,34 @@ namespace party { - std::string get_www_url(); + struct connection_state + { + game::netadr_s host; + std::string challenge; + bool hostDefined; + std::string motd; + int max_clients; + std::string base_url; + }; + + struct discord_information + { + std::string image; + std::string image_text; + }; + void user_download_response(bool response); void menu_error(const std::string& error); - void reset_connect_state(); + void reset_server_connection_state(); void connect(const game::netadr_s& target); void start_map(const std::string& mapname, bool dev = false); void clear_sv_motd(); - game::netadr_s get_state_host(); - int server_client_count(); - std::string get_discord_server_image(); - std::string get_discord_server_text(); + connection_state get_server_connection_state(); + discord_information get_server_discord_information(); int get_client_num_by_name(const std::string& name); diff --git a/src/client/component/server_list.cpp b/src/client/component/server_list.cpp index 5f536c72..e3577e2e 100644 --- a/src/client/component/server_list.cpp +++ b/src/client/component/server_list.cpp @@ -77,7 +77,7 @@ namespace server_list server_list_page = 0; } - party::reset_connect_state(); + party::reset_server_connection_state(); if (get_master_server(master_state.address)) { diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index 3ab187d7..2bc4aeef 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -367,7 +367,7 @@ namespace ui_scripting download_table["abort"] = download::stop_download; download_table["userdownloadresponse"] = party::user_download_response; - download_table["getwwwurl"] = party::get_www_url; + download_table["getwwwurl"] = party::get_server_connection_state().base_url; } void start()