cleanup discord and party [skip ci]

This commit is contained in:
m 2023-12-15 14:12:36 -06:00
parent fe86fc31e1
commit 5acb5f7ba0
5 changed files with 249 additions and 263 deletions

View File

@ -31,23 +31,10 @@ namespace discord
{
DiscordRichPresence discord_presence;
void update_discord()
{
if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded())
void update_discord_frontend()
{
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)
@ -55,118 +42,122 @@ namespace discord
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;
discord_presence.state = "Main Menu";
discord_presence.largeImageKey = SELECT_VALUE("menu_singleplayer", "menu_multiplayer");
}
if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE, presence_key) && !game::DB_IsXAssetDefault(game::ASSET_TYPE_LOCALIZE, presence_key))
Discord_UpdatePresence(&discord_presence);
}
void update_discord_ingame()
{
clean_mapname = game::UI_SafeTranslateString(presence_key);
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())
{
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));
// 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));
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<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.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;
}
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))
if (game::SV_Loaded())
{
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();
}
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
{
discord_presence.smallImageKey = "";
discord_presence.largeImageKey = discord_map_image_asset;
discord_presence.largeImageText = "";
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<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))
);
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.state = "";
discord_presence.largeImageKey = mapname;
discord_presence.smallImageKey = "";
strcpy_s(details, 0x80, clean_mapname);
discord_presence.details = mapname;
}
discord_presence.details = details;
if (!discord_presence.startTimestamp)
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_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)
void ready(const DiscordUser* request)
{
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_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;
};
}

View File

@ -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<const char*>(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);
});

View File

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

View File

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

View File

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