mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 17:52:08 -10:00
Some code refactoring to allow mission titles to be strings of any length. Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected. Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated. Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
305 lines
6.6 KiB
C++
305 lines
6.6 KiB
C++
#include "discord.h"
|
|
#include "freespace.h"
|
|
#include "events/events.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "io/timer.h"
|
|
#include "mission/missioncampaign.h"
|
|
#include "mission/missionparse.h"
|
|
#include "parse/parselo.h"
|
|
#include "playerman/player.h"
|
|
|
|
#if WITH_DISCORD
|
|
|
|
#include "discord_rpc.h"
|
|
|
|
namespace {
|
|
struct PresenceInfo {
|
|
SCP_string state;
|
|
int64_t timestamp;
|
|
};
|
|
|
|
bool initialized = false;
|
|
bool discord_ready = false;
|
|
bool events_added = false;
|
|
UI_TIMESTAMP next_mission_update = UI_TIMESTAMP::invalid();
|
|
|
|
bool in_mission = false;
|
|
|
|
PresenceInfo current_info;
|
|
|
|
const char* APPLICATION_ID = "465270111440470016";
|
|
|
|
SCP_unordered_map<SCP_string, SCP_string> campaign_name_cache;
|
|
|
|
SCP_string get_campaign_name(const char* filename)
|
|
{
|
|
SCP_string name = filename;
|
|
|
|
auto iter = campaign_name_cache.find(name);
|
|
if (iter != campaign_name_cache.end()) {
|
|
return iter->second;
|
|
}
|
|
|
|
auto campaign_name = mission_campaign_get_name(filename);
|
|
campaign_name_cache[name] = campaign_name;
|
|
|
|
return campaign_name;
|
|
}
|
|
|
|
SCP_string get_current_campaign_name()
|
|
{
|
|
if (Game_mode & GM_CAMPAIGN_MODE) {
|
|
return Campaign.name;
|
|
} else if (Player != nullptr) {
|
|
return get_campaign_name(Player->current_campaign);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
SCP_string get_details()
|
|
{
|
|
SCP_string res;
|
|
|
|
auto has_campaign = Player != nullptr && strlen(Player->current_campaign) > 0;
|
|
if (in_mission && !(Game_mode & GM_CAMPAIGN_MODE)) {
|
|
// This is a standalone mission so we don't actually have a campaign
|
|
has_campaign = false;
|
|
}
|
|
|
|
if (has_campaign && in_mission) {
|
|
sprintf(res, "%s: %s", get_current_campaign_name().c_str(), The_mission.name.c_str());
|
|
} else if (has_campaign) {
|
|
sprintf(res, "Campaign %s", get_current_campaign_name().c_str());
|
|
} else if (in_mission) {
|
|
res = The_mission.name;
|
|
} else {
|
|
res = "In game";
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void update_presence() {
|
|
auto details = get_details();
|
|
|
|
DiscordRichPresence presence;
|
|
memset(&presence, 0, sizeof(presence));
|
|
presence.details = details.c_str();
|
|
presence.state = current_info.state.c_str();
|
|
presence.startTimestamp = current_info.timestamp;
|
|
|
|
Discord_UpdatePresence(&presence);
|
|
}
|
|
|
|
void set_presence(const SCP_string& state, int64_t timestamp = 0) {
|
|
if (current_info.state == state && current_info.timestamp == timestamp) {
|
|
// No changes
|
|
return;
|
|
}
|
|
|
|
current_info.state = state;
|
|
current_info.timestamp = timestamp;
|
|
|
|
if (discord_ready) {
|
|
update_presence();
|
|
}
|
|
}
|
|
|
|
void set_game_play_presence()
|
|
{
|
|
SCP_string state;
|
|
|
|
sprintf(state, "In mission (%d %s)", Player->stats.m_kill_count_ok,
|
|
Player->stats.m_kill_count_ok == 1 ? "kill" : "kills");
|
|
|
|
set_presence(state, (int64_t)time(nullptr) - f2i(Missiontime));
|
|
|
|
// Update this every 20 seconds since Discord already has a 15 second rate-limit
|
|
next_mission_update = ui_timestamp(20 * MILLISECONDS_PER_SECOND);
|
|
}
|
|
|
|
void update_discord()
|
|
{
|
|
if (!initialized)
|
|
return;
|
|
|
|
Discord_RunCallbacks();
|
|
|
|
if (gameseq_get_state() == GS_STATE_GAME_PLAY && ui_timestamp_elapsed(next_mission_update)) {
|
|
set_game_play_presence();
|
|
}
|
|
}
|
|
|
|
void shutdown_discord()
|
|
{
|
|
if (!initialized)
|
|
return;
|
|
|
|
Discord_Shutdown();
|
|
initialized = false;
|
|
discord_ready = false;
|
|
}
|
|
|
|
void handleEnterState(int old_state, int new_state)
|
|
{
|
|
if (!initialized)
|
|
return;
|
|
|
|
if (old_state == new_state) {
|
|
return;
|
|
}
|
|
|
|
if (new_state == GS_STATE_GAME_PLAY) {
|
|
// Update immediately if we enter the game play state again
|
|
set_game_play_presence();
|
|
return;
|
|
}
|
|
|
|
if (gameseq_get_state_idx(GS_STATE_GAME_PLAY) >= 0) {
|
|
// Special case for when we come out of game play
|
|
switch (new_state) {
|
|
case GS_STATE_OPTIONS_MENU:
|
|
case GS_STATE_HOTKEY_SCREEN:
|
|
case GS_STATE_GAMEPLAY_HELP:
|
|
case GS_STATE_MISSION_LOG_SCROLLBACK:
|
|
set_presence("In game menu");
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (new_state) {
|
|
case GS_STATE_MAIN_MENU:
|
|
set_presence("In main hall");
|
|
break;
|
|
case GS_STATE_BRIEFING:
|
|
set_presence("In mission briefing");
|
|
break;
|
|
case GS_STATE_CMD_BRIEF:
|
|
set_presence("In command briefing");
|
|
break;
|
|
case GS_EVENT_DEBRIEF:
|
|
set_presence("In debriefing");
|
|
break;
|
|
case GS_STATE_GAME_PAUSED:
|
|
set_presence("Paused");
|
|
break;
|
|
case GS_STATE_RED_ALERT:
|
|
set_presence("In red-alert briefing");
|
|
break;
|
|
case GS_STATE_INITIAL_PLAYER_SELECT:
|
|
set_presence("Selecting player");
|
|
break;
|
|
case GS_STATE_FICTION_VIEWER:
|
|
set_presence("In fiction viewer");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void handleLeaveState(int old_state, int /*new_state*/)
|
|
{
|
|
if (!initialized)
|
|
return;
|
|
|
|
if (old_state == GS_STATE_GAME_PAUSED) {
|
|
// Reset the game play state immediately
|
|
set_game_play_presence();
|
|
}
|
|
|
|
if (old_state == GS_STATE_GAME_PLAY) {
|
|
in_mission = false;
|
|
next_mission_update = UI_TIMESTAMP::invalid();
|
|
}
|
|
}
|
|
|
|
void handleMissionLoad(const char* /*filename*/)
|
|
{
|
|
if (!initialized)
|
|
return;
|
|
|
|
set_presence("Loading mission");
|
|
in_mission = true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace libs {
|
|
namespace discord {
|
|
|
|
void init()
|
|
{
|
|
//If we've already initialized then just return
|
|
if (initialized)
|
|
return;
|
|
|
|
DiscordEventHandlers handlers;
|
|
memset(&handlers, 0, sizeof(handlers));
|
|
handlers.ready = [](const DiscordUser* connectedUser) {
|
|
mprintf(("Discord: connected to user %s#%s - %s\n", connectedUser->username, connectedUser->discriminator,
|
|
connectedUser->userId));
|
|
discord_ready = true;
|
|
update_presence();
|
|
};
|
|
handlers.errored = [](int errcode, const char* message) {
|
|
mprintf(("Discord: error (%d: %s)\n", errcode, message));
|
|
discord_ready = false;
|
|
};
|
|
handlers.disconnected = [](int errcode, const char* message) {
|
|
mprintf(("Discord: disconnected (%d: %s)\n", errcode, message));
|
|
discord_ready = false;
|
|
};
|
|
|
|
Discord_Initialize(APPLICATION_ID, &handlers, 0, nullptr);
|
|
|
|
//Only ever register these events once even if discord presence is toggled back on during the same game instance -Mjn
|
|
if (!events_added) {
|
|
events::EngineUpdate.add(update_discord);
|
|
events::EngineShutdown.add(shutdown_discord);
|
|
events::GameEnterState.add(handleEnterState);
|
|
events::GameLeaveState.add(handleLeaveState);
|
|
events::GameMissionLoad.add(handleMissionLoad);
|
|
|
|
events_added = true;
|
|
}
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
void shutdown()
|
|
{
|
|
shutdown_discord();
|
|
}
|
|
|
|
void set_presence_string(const SCP_string &text)
|
|
{
|
|
set_presence(text);
|
|
}
|
|
|
|
void set_presence_gameplay()
|
|
{
|
|
set_game_play_presence();
|
|
}
|
|
|
|
} // namespace discord
|
|
} // namespace libs
|
|
|
|
#else
|
|
// Stubs for building without Discord support.
|
|
namespace libs {
|
|
namespace discord {
|
|
|
|
void init() {
|
|
mprintf(("Cannot create Discord session. Not built with Discord support.\n"));
|
|
}
|
|
void shutdown() {}
|
|
void set_presence_string(const SCP_string &text) {}
|
|
void set_presence_gameplay() {}
|
|
|
|
} // namespace discord
|
|
} // namespace libs
|
|
#endif
|