Skip to content

Commit

Permalink
Discord Rich Presence support (#3167)
Browse files Browse the repository at this point in the history
* get draft from #2577

* get draft from #2577

* add checkbox to allow connecting with discord-rpc

test case with load data

* safe delete only if used

* fix vars when game is launching

* add function to set appid and small fixes

* added functions to manage assets, small fixes and bugs

* rewriten setAsset, refactor littlebit

* add function to check client connection with discord

* add buttons to rpc

* minor amendments, renaming of functions

* remove update discord-rpc from build

* thats too

* Memory leak fixed, presence updated on app change

* Presence state change, change of time variable

* Fixed unable to change custom state

* Added const to IsDiscordRPCEnabled

* Added missing argument validation

* Exclude vendor files, added premake script

* Re-run actions

* Bump discord-rpc version

* Fix broken discord tag_name

* Bump discord-rpc version

* Revert to 2b42cd9

* Bump discord-rpc version

* Disabled data customization if the app is not own

* Change DEFAULT_APP_ID

* Remove comment

---------

Co-authored-by: Lpsd <[email protected]>
  • Loading branch information
znvjder and Lpsd authored Oct 11, 2023
1 parent 2846e27 commit fdaa3ac
Show file tree
Hide file tree
Showing 21 changed files with 958 additions and 41 deletions.
5 changes: 3 additions & 2 deletions Client/core/CClientVariables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,9 @@ void CClientVariables::LoadDefaults()
DEFAULT("browser_remote_websites", true); // Load remote websites?
DEFAULT("browser_remote_javascript", true); // Execute javascript on remote websites?
DEFAULT("filter_duplicate_log_lines", true); // Filter duplicate log lines for debug view and clientscript.log
DEFAULT("always_show_transferbox", false); // Should the transfer box always be visible for downloads? (and ignore scripted control)
DEFAULT("_beta_qc_rightclick_command", _S("reconnect")); // Command to run when right clicking quick connect (beta - can be removed at any time)
DEFAULT("always_show_transferbox", false); // Should the transfer box always be visible for downloads? (and ignore scripted control)
DEFAULT("allow_discord_rpc", false); // Enable Discord Rich Presence
DEFAULT("_beta_qc_rightclick_command", _S("reconnect")); // Command to run when right clicking quick connect (beta - can be removed at any time)

if (!Exists("locale"))
{
Expand Down
32 changes: 32 additions & 0 deletions Client/core/CCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "CModelCacheManager.h"
#include <SharedUtil.Detours.h>
#include <ServerBrowser/CServerCache.h>
#include "CDiscordRichPresence.h"

using SharedUtil::CalcMTASAPath;
using namespace std;
Expand Down Expand Up @@ -160,12 +161,19 @@ CCore::CCore()

// Create tray icon
m_pTrayIcon = new CTrayIcon();

// Create discord rich presence
m_pDiscordRichPresence = std::shared_ptr<CDiscordRichPresence>(new CDiscordRichPresence());
}

CCore::~CCore()
{
WriteDebugEvent("CCore::~CCore");

// Reset Discord rich presence
if (m_pDiscordRichPresence)
m_pDiscordRichPresence.reset();

// Destroy tray icon
delete m_pTrayIcon;

Expand Down Expand Up @@ -654,6 +662,20 @@ void CCore::SetConnected(bool bConnected)
{
m_pLocalGUI->GetMainMenu()->SetIsIngame(bConnected);
UpdateIsWindowMinimized(); // Force update of stuff

if (g_pCore->GetCVars()->GetValue("allow_discord_rpc", false))
{
auto discord = g_pCore->GetDiscord();
if (!discord->IsDiscordRPCEnabled())
discord->SetDiscordRPCEnabled(true);

discord->SetPresenceState(bConnected ? "In-game" : "Main menu", false);
discord->SetPresenceStartTimestamp(0);
discord->SetPresenceDetails("");

if (bConnected)
discord->SetPresenceStartTimestamp(time(nullptr));
}
}

bool CCore::IsConnected()
Expand Down Expand Up @@ -1315,6 +1337,10 @@ void CCore::DoPostFramePulse()
GetGraphStats()->Draw();
m_pConnectManager->DoPulse();

static auto discord = g_pCore->GetDiscord();
if (discord && discord->IsDiscordRPCEnabled())
discord->UpdatePresence();

TIMING_CHECKPOINT("-CorePostFrame2");
}

Expand Down Expand Up @@ -2361,3 +2387,9 @@ size_t CCore::GetStreamingMemory()
? m_CustomStreamingMemoryLimitBytes
: CVARS_GET_VALUE<size_t>("streaming_memory") * 1024 * 1024; // MB to B conversion
}

// Discord rich presence
std::shared_ptr<CDiscordInterface> CCore::GetDiscord()
{
return m_pDiscordRichPresence;
}
50 changes: 27 additions & 23 deletions Client/core/CCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*****************************************************************************/

class CCore;
class CDiscordRichPresence;
class CDiscordInterface;

#pragma once

Expand Down Expand Up @@ -82,25 +84,26 @@ class CCore : public CCoreInterface, public CSingleton<CCore>
~CCore();

// Subsystems (query)
eCoreVersion GetVersion();
CConsoleInterface* GetConsole();
CCommandsInterface* GetCommands();
CConnectManager* GetConnectManager() { return m_pConnectManager; };
CGame* GetGame();
CGUI* GetGUI();
CGraphicsInterface* GetGraphics();
CModManagerInterface* GetModManager();
CMultiplayer* GetMultiplayer();
CNet* GetNetwork();
CXML* GetXML() { return m_pXML; };
CXMLNode* GetConfig();
CClientVariables* GetCVars() { return &m_ClientVariables; };
CKeyBindsInterface* GetKeyBinds();
CMouseControl* GetMouseControl() { return m_pMouseControl; };
CLocalGUI* GetLocalGUI();
CLocalizationInterface* GetLocalization() { return g_pLocalization; };
CWebCoreInterface* GetWebCore();
CTrayIconInterface* GetTrayIcon() { return m_pTrayIcon; };
eCoreVersion GetVersion();
CConsoleInterface* GetConsole();
CCommandsInterface* GetCommands();
CConnectManager* GetConnectManager() { return m_pConnectManager; };
CGame* GetGame();
CGUI* GetGUI();
CGraphicsInterface* GetGraphics();
CModManagerInterface* GetModManager();
CMultiplayer* GetMultiplayer();
CNet* GetNetwork();
CXML* GetXML() { return m_pXML; };
CXMLNode* GetConfig();
CClientVariables* GetCVars() { return &m_ClientVariables; };
CKeyBindsInterface* GetKeyBinds();
CMouseControl* GetMouseControl() { return m_pMouseControl; };
CLocalGUI* GetLocalGUI();
CLocalizationInterface* GetLocalization() { return g_pLocalization; };
CWebCoreInterface* GetWebCore();
CTrayIconInterface* GetTrayIcon() { return m_pTrayIcon; };
std::shared_ptr<CDiscordInterface> GetDiscord();

void SaveConfig(bool bWaitUntilFinished = false);

Expand Down Expand Up @@ -296,10 +299,11 @@ class CCore : public CCoreInterface, public CSingleton<CCore>
CModelCacheManager* m_pModelCacheManager;

// Instances (put new classes here!)
CXMLFile* m_pConfigFile;
CClientVariables m_ClientVariables;
CWebCoreInterface* m_pWebCore = nullptr;
CTrayIcon* m_pTrayIcon;
CXMLFile* m_pConfigFile;
CClientVariables m_ClientVariables;
CWebCoreInterface* m_pWebCore = nullptr;
CTrayIcon* m_pTrayIcon;
std::shared_ptr<CDiscordRichPresence> m_pDiscordRichPresence;

// Hook interfaces.
CMessageLoopHook* m_pMessageLoopHook;
Expand Down
227 changes: 227 additions & 0 deletions Client/core/CDiscordRichPresence.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*****************************************************************************
*
* PROJECT: Multi Theft Auto v1.0
* LICENSE: See LICENSE in the top level directory
* FILE: core/CDiscordRichPresence.cpp
* PURPOSE: Discord rich presence implementation
*
* Multi Theft Auto is available from http://www.multitheftauto.com/
*
*****************************************************************************/

#include "StdInc.h"
#include "discord_rpc.h"
#include "CDiscordRichPresence.h"

constexpr char DEFAULT_APP_ID[] = "468493322583801867";
constexpr char DEFAULT_APP_ASSET[] = "mta_logo_round";
constexpr char DEFAULT_APP_ASSET_TEXT[] = "Multi Theft Auto";
constexpr char DEFAULT_APP_ASSET_SMALL[] = "";
constexpr char DEFAULT_APP_ASSET_SMALL_TEXT[] = "";

CDiscordRichPresence::CDiscordRichPresence() : m_uiDiscordAppStart(0), m_uiDiscordAppEnd(0)
{
SetDefaultData();

m_strDiscordAppState.clear();
}

CDiscordRichPresence::~CDiscordRichPresence()
{
if (m_bDiscordRPCEnabled)
ShutdownDiscord();
}

void CDiscordRichPresence::InitializeDiscord()
{
DiscordEventHandlers handlers;
memset(&handlers, 0, sizeof(handlers));

// Handlers .ready .disconnected .errored maybe use in future?
Discord_Initialize((m_strDiscordAppCurrentId.empty()) ? DEFAULT_APP_ID : m_strDiscordAppCurrentId.c_str(), &handlers, 1, nullptr);

m_bDisallowCustomDetails = (m_strDiscordAppCurrentId == DEFAULT_APP_ID) ? true : false;
}

void CDiscordRichPresence::ShutdownDiscord()
{
Discord_Shutdown();
}

void CDiscordRichPresence::RestartDiscord()
{
ShutdownDiscord();
InitializeDiscord();
}

void CDiscordRichPresence::SetDefaultData()
{
m_strDiscordAppId = DEFAULT_APP_ID;
m_strDiscordAppAsset = DEFAULT_APP_ASSET;
m_strDiscordAppAssetText = DEFAULT_APP_ASSET_TEXT;

m_strDiscordAppAssetSmall = DEFAULT_APP_ASSET_SMALL;
m_strDiscordAppAssetSmallText = DEFAULT_APP_ASSET_SMALL_TEXT;

m_strDiscordAppCurrentId = DEFAULT_APP_ID;
m_strDiscordAppDetails.clear();
m_strDiscordAppCustomState.clear();

m_aButtons = {};
m_bUpdateRichPresence = true;
m_bDisallowCustomDetails = true;
}

void CDiscordRichPresence::UpdatePresence()
{
if (!m_bUpdateRichPresence)
return;

DiscordRichPresence discordPresence;
memset(&discordPresence, 0, sizeof(discordPresence));

discordPresence.largeImageKey = m_strDiscordAppAsset.c_str();
discordPresence.largeImageText = m_strDiscordAppAssetText.c_str();
discordPresence.smallImageKey = m_strDiscordAppAssetSmall.c_str();
discordPresence.smallImageText = m_strDiscordAppAssetSmallText.c_str();

discordPresence.state = (!m_strDiscordAppCustomState.empty() || !m_bDisallowCustomDetails) ? m_strDiscordAppCustomState.c_str() : m_strDiscordAppState.c_str();

discordPresence.details = m_strDiscordAppDetails.c_str();
discordPresence.startTimestamp = m_uiDiscordAppStart;

DiscordButton buttons[2];
if (m_aButtons)
{
buttons[0].label = std::get<0>(*m_aButtons).first.c_str();
buttons[0].url = std::get<0>(*m_aButtons).second.c_str();
buttons[1].label = std::get<1>(*m_aButtons).first.c_str();
buttons[1].url = std::get<1>(*m_aButtons).second.c_str();

discordPresence.buttons = buttons;
}

Discord_UpdatePresence(&discordPresence);
m_bUpdateRichPresence = false;
}

void CDiscordRichPresence::SetPresenceStartTimestamp(const unsigned long ulStart)
{
m_uiDiscordAppStart = ulStart;
m_bUpdateRichPresence = true;
}

void CDiscordRichPresence::SetAssetLargeData(const char* szAsset, const char* szAssetText)
{
SetAsset(szAsset, szAssetText, true);
}

void CDiscordRichPresence::SetAssetSmallData(const char* szAsset, const char* szAssetText)
{
SetAsset(szAsset, szAssetText, false);
}

void CDiscordRichPresence::SetAsset(const char* szAsset, const char* szAssetText, bool isLarge)
{
if (isLarge)
{
m_strDiscordAppAsset = (szAsset && *szAsset) ? szAsset : DEFAULT_APP_ASSET;
m_strDiscordAppAssetText = (szAssetText && *szAssetText) ? szAssetText : DEFAULT_APP_ASSET_TEXT;
}
else
{
m_strDiscordAppAssetSmall = (szAsset && *szAsset) ? szAsset : DEFAULT_APP_ASSET_SMALL;
m_strDiscordAppAssetSmallText = (szAssetText && *szAssetText) ? szAssetText : DEFAULT_APP_ASSET_SMALL_TEXT;
}
m_bUpdateRichPresence = true;
}

bool CDiscordRichPresence::SetPresenceState(const char* szState, bool bCustom)
{
if (bCustom)
m_strDiscordAppCustomState = szState;
else
m_strDiscordAppState = szState;

m_bUpdateRichPresence = true;
return true;
}

bool CDiscordRichPresence::SetPresenceButtons(unsigned short int iIndex, const char* szName, const char* szUrl)
{
// Should it always return true?
if (iIndex <= 2)
{
std::decay_t<decltype(*m_aButtons)> buttons;
if (m_aButtons)
buttons = *m_aButtons;

if (iIndex == 1)
std::get<0>(buttons) = {szName, szUrl};
else if (iIndex == 2)
std::get<1>(buttons) = {szName, szUrl};

m_aButtons = buttons;
m_bUpdateRichPresence = true;

return true;
}

return false;
}

bool CDiscordRichPresence::SetPresenceDetails(const char* szDetails, bool bCustom)
{
m_strDiscordAppDetails = szDetails;
m_bUpdateRichPresence = true;
return true;
}

bool CDiscordRichPresence::ResetDiscordData()
{
SetDefaultData();

if (m_bDiscordRPCEnabled)
{
RestartDiscord();
m_bUpdateRichPresence = true;
}
return true;
}

bool CDiscordRichPresence::SetApplicationID(const char* szAppID)
{
m_strDiscordAppCurrentId = (szAppID && *szAppID) ? szAppID : DEFAULT_APP_ID;

if (m_bDiscordRPCEnabled)
{
RestartDiscord();
m_bUpdateRichPresence = true;
}
return true;
}

bool CDiscordRichPresence::SetDiscordRPCEnabled(bool bEnabled)
{
m_bDiscordRPCEnabled = bEnabled;

if (!bEnabled)
{
ShutdownDiscord();
return true;
}

InitializeDiscord();
m_bUpdateRichPresence = true;
return true;
}

bool CDiscordRichPresence::IsDiscordRPCEnabled() const
{
return m_bDiscordRPCEnabled;
}

bool CDiscordRichPresence::IsDiscordCustomDetailsDisallowed() const
{
return m_bDisallowCustomDetails;
}
Loading

1 comment on commit fdaa3ac

@qaisjp
Copy link
Contributor

@qaisjp qaisjp commented on fdaa3ac Oct 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congratulations & well done

Please sign in to comment.