Skip to content

Commit fdaa3ac

Browse files
znvjderLpsd
andauthored
Discord Rich Presence support (#3167)
* 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]>
1 parent 2846e27 commit fdaa3ac

21 files changed

+958
-41
lines changed

Client/core/CClientVariables.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,9 @@ void CClientVariables::LoadDefaults()
353353
DEFAULT("browser_remote_websites", true); // Load remote websites?
354354
DEFAULT("browser_remote_javascript", true); // Execute javascript on remote websites?
355355
DEFAULT("filter_duplicate_log_lines", true); // Filter duplicate log lines for debug view and clientscript.log
356-
DEFAULT("always_show_transferbox", false); // Should the transfer box always be visible for downloads? (and ignore scripted control)
357-
DEFAULT("_beta_qc_rightclick_command", _S("reconnect")); // Command to run when right clicking quick connect (beta - can be removed at any time)
356+
DEFAULT("always_show_transferbox", false); // Should the transfer box always be visible for downloads? (and ignore scripted control)
357+
DEFAULT("allow_discord_rpc", false); // Enable Discord Rich Presence
358+
DEFAULT("_beta_qc_rightclick_command", _S("reconnect")); // Command to run when right clicking quick connect (beta - can be removed at any time)
358359

359360
if (!Exists("locale"))
360361
{

Client/core/CCore.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "CModelCacheManager.h"
2626
#include <SharedUtil.Detours.h>
2727
#include <ServerBrowser/CServerCache.h>
28+
#include "CDiscordRichPresence.h"
2829

2930
using SharedUtil::CalcMTASAPath;
3031
using namespace std;
@@ -160,12 +161,19 @@ CCore::CCore()
160161

161162
// Create tray icon
162163
m_pTrayIcon = new CTrayIcon();
164+
165+
// Create discord rich presence
166+
m_pDiscordRichPresence = std::shared_ptr<CDiscordRichPresence>(new CDiscordRichPresence());
163167
}
164168

165169
CCore::~CCore()
166170
{
167171
WriteDebugEvent("CCore::~CCore");
168172

173+
// Reset Discord rich presence
174+
if (m_pDiscordRichPresence)
175+
m_pDiscordRichPresence.reset();
176+
169177
// Destroy tray icon
170178
delete m_pTrayIcon;
171179

@@ -654,6 +662,20 @@ void CCore::SetConnected(bool bConnected)
654662
{
655663
m_pLocalGUI->GetMainMenu()->SetIsIngame(bConnected);
656664
UpdateIsWindowMinimized(); // Force update of stuff
665+
666+
if (g_pCore->GetCVars()->GetValue("allow_discord_rpc", false))
667+
{
668+
auto discord = g_pCore->GetDiscord();
669+
if (!discord->IsDiscordRPCEnabled())
670+
discord->SetDiscordRPCEnabled(true);
671+
672+
discord->SetPresenceState(bConnected ? "In-game" : "Main menu", false);
673+
discord->SetPresenceStartTimestamp(0);
674+
discord->SetPresenceDetails("");
675+
676+
if (bConnected)
677+
discord->SetPresenceStartTimestamp(time(nullptr));
678+
}
657679
}
658680

659681
bool CCore::IsConnected()
@@ -1315,6 +1337,10 @@ void CCore::DoPostFramePulse()
13151337
GetGraphStats()->Draw();
13161338
m_pConnectManager->DoPulse();
13171339

1340+
static auto discord = g_pCore->GetDiscord();
1341+
if (discord && discord->IsDiscordRPCEnabled())
1342+
discord->UpdatePresence();
1343+
13181344
TIMING_CHECKPOINT("-CorePostFrame2");
13191345
}
13201346

@@ -2361,3 +2387,9 @@ size_t CCore::GetStreamingMemory()
23612387
? m_CustomStreamingMemoryLimitBytes
23622388
: CVARS_GET_VALUE<size_t>("streaming_memory") * 1024 * 1024; // MB to B conversion
23632389
}
2390+
2391+
// Discord rich presence
2392+
std::shared_ptr<CDiscordInterface> CCore::GetDiscord()
2393+
{
2394+
return m_pDiscordRichPresence;
2395+
}

Client/core/CCore.h

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
*****************************************************************************/
1111

1212
class CCore;
13+
class CDiscordRichPresence;
14+
class CDiscordInterface;
1315

1416
#pragma once
1517

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

8486
// Subsystems (query)
85-
eCoreVersion GetVersion();
86-
CConsoleInterface* GetConsole();
87-
CCommandsInterface* GetCommands();
88-
CConnectManager* GetConnectManager() { return m_pConnectManager; };
89-
CGame* GetGame();
90-
CGUI* GetGUI();
91-
CGraphicsInterface* GetGraphics();
92-
CModManagerInterface* GetModManager();
93-
CMultiplayer* GetMultiplayer();
94-
CNet* GetNetwork();
95-
CXML* GetXML() { return m_pXML; };
96-
CXMLNode* GetConfig();
97-
CClientVariables* GetCVars() { return &m_ClientVariables; };
98-
CKeyBindsInterface* GetKeyBinds();
99-
CMouseControl* GetMouseControl() { return m_pMouseControl; };
100-
CLocalGUI* GetLocalGUI();
101-
CLocalizationInterface* GetLocalization() { return g_pLocalization; };
102-
CWebCoreInterface* GetWebCore();
103-
CTrayIconInterface* GetTrayIcon() { return m_pTrayIcon; };
87+
eCoreVersion GetVersion();
88+
CConsoleInterface* GetConsole();
89+
CCommandsInterface* GetCommands();
90+
CConnectManager* GetConnectManager() { return m_pConnectManager; };
91+
CGame* GetGame();
92+
CGUI* GetGUI();
93+
CGraphicsInterface* GetGraphics();
94+
CModManagerInterface* GetModManager();
95+
CMultiplayer* GetMultiplayer();
96+
CNet* GetNetwork();
97+
CXML* GetXML() { return m_pXML; };
98+
CXMLNode* GetConfig();
99+
CClientVariables* GetCVars() { return &m_ClientVariables; };
100+
CKeyBindsInterface* GetKeyBinds();
101+
CMouseControl* GetMouseControl() { return m_pMouseControl; };
102+
CLocalGUI* GetLocalGUI();
103+
CLocalizationInterface* GetLocalization() { return g_pLocalization; };
104+
CWebCoreInterface* GetWebCore();
105+
CTrayIconInterface* GetTrayIcon() { return m_pTrayIcon; };
106+
std::shared_ptr<CDiscordInterface> GetDiscord();
104107

105108
void SaveConfig(bool bWaitUntilFinished = false);
106109

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

298301
// Instances (put new classes here!)
299-
CXMLFile* m_pConfigFile;
300-
CClientVariables m_ClientVariables;
301-
CWebCoreInterface* m_pWebCore = nullptr;
302-
CTrayIcon* m_pTrayIcon;
302+
CXMLFile* m_pConfigFile;
303+
CClientVariables m_ClientVariables;
304+
CWebCoreInterface* m_pWebCore = nullptr;
305+
CTrayIcon* m_pTrayIcon;
306+
std::shared_ptr<CDiscordRichPresence> m_pDiscordRichPresence;
303307

304308
// Hook interfaces.
305309
CMessageLoopHook* m_pMessageLoopHook;

Client/core/CDiscordRichPresence.cpp

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto v1.0
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: core/CDiscordRichPresence.cpp
6+
* PURPOSE: Discord rich presence implementation
7+
*
8+
* Multi Theft Auto is available from http://www.multitheftauto.com/
9+
*
10+
*****************************************************************************/
11+
12+
#include "StdInc.h"
13+
#include "discord_rpc.h"
14+
#include "CDiscordRichPresence.h"
15+
16+
constexpr char DEFAULT_APP_ID[] = "468493322583801867";
17+
constexpr char DEFAULT_APP_ASSET[] = "mta_logo_round";
18+
constexpr char DEFAULT_APP_ASSET_TEXT[] = "Multi Theft Auto";
19+
constexpr char DEFAULT_APP_ASSET_SMALL[] = "";
20+
constexpr char DEFAULT_APP_ASSET_SMALL_TEXT[] = "";
21+
22+
CDiscordRichPresence::CDiscordRichPresence() : m_uiDiscordAppStart(0), m_uiDiscordAppEnd(0)
23+
{
24+
SetDefaultData();
25+
26+
m_strDiscordAppState.clear();
27+
}
28+
29+
CDiscordRichPresence::~CDiscordRichPresence()
30+
{
31+
if (m_bDiscordRPCEnabled)
32+
ShutdownDiscord();
33+
}
34+
35+
void CDiscordRichPresence::InitializeDiscord()
36+
{
37+
DiscordEventHandlers handlers;
38+
memset(&handlers, 0, sizeof(handlers));
39+
40+
// Handlers .ready .disconnected .errored maybe use in future?
41+
Discord_Initialize((m_strDiscordAppCurrentId.empty()) ? DEFAULT_APP_ID : m_strDiscordAppCurrentId.c_str(), &handlers, 1, nullptr);
42+
43+
m_bDisallowCustomDetails = (m_strDiscordAppCurrentId == DEFAULT_APP_ID) ? true : false;
44+
}
45+
46+
void CDiscordRichPresence::ShutdownDiscord()
47+
{
48+
Discord_Shutdown();
49+
}
50+
51+
void CDiscordRichPresence::RestartDiscord()
52+
{
53+
ShutdownDiscord();
54+
InitializeDiscord();
55+
}
56+
57+
void CDiscordRichPresence::SetDefaultData()
58+
{
59+
m_strDiscordAppId = DEFAULT_APP_ID;
60+
m_strDiscordAppAsset = DEFAULT_APP_ASSET;
61+
m_strDiscordAppAssetText = DEFAULT_APP_ASSET_TEXT;
62+
63+
m_strDiscordAppAssetSmall = DEFAULT_APP_ASSET_SMALL;
64+
m_strDiscordAppAssetSmallText = DEFAULT_APP_ASSET_SMALL_TEXT;
65+
66+
m_strDiscordAppCurrentId = DEFAULT_APP_ID;
67+
m_strDiscordAppDetails.clear();
68+
m_strDiscordAppCustomState.clear();
69+
70+
m_aButtons = {};
71+
m_bUpdateRichPresence = true;
72+
m_bDisallowCustomDetails = true;
73+
}
74+
75+
void CDiscordRichPresence::UpdatePresence()
76+
{
77+
if (!m_bUpdateRichPresence)
78+
return;
79+
80+
DiscordRichPresence discordPresence;
81+
memset(&discordPresence, 0, sizeof(discordPresence));
82+
83+
discordPresence.largeImageKey = m_strDiscordAppAsset.c_str();
84+
discordPresence.largeImageText = m_strDiscordAppAssetText.c_str();
85+
discordPresence.smallImageKey = m_strDiscordAppAssetSmall.c_str();
86+
discordPresence.smallImageText = m_strDiscordAppAssetSmallText.c_str();
87+
88+
discordPresence.state = (!m_strDiscordAppCustomState.empty() || !m_bDisallowCustomDetails) ? m_strDiscordAppCustomState.c_str() : m_strDiscordAppState.c_str();
89+
90+
discordPresence.details = m_strDiscordAppDetails.c_str();
91+
discordPresence.startTimestamp = m_uiDiscordAppStart;
92+
93+
DiscordButton buttons[2];
94+
if (m_aButtons)
95+
{
96+
buttons[0].label = std::get<0>(*m_aButtons).first.c_str();
97+
buttons[0].url = std::get<0>(*m_aButtons).second.c_str();
98+
buttons[1].label = std::get<1>(*m_aButtons).first.c_str();
99+
buttons[1].url = std::get<1>(*m_aButtons).second.c_str();
100+
101+
discordPresence.buttons = buttons;
102+
}
103+
104+
Discord_UpdatePresence(&discordPresence);
105+
m_bUpdateRichPresence = false;
106+
}
107+
108+
void CDiscordRichPresence::SetPresenceStartTimestamp(const unsigned long ulStart)
109+
{
110+
m_uiDiscordAppStart = ulStart;
111+
m_bUpdateRichPresence = true;
112+
}
113+
114+
void CDiscordRichPresence::SetAssetLargeData(const char* szAsset, const char* szAssetText)
115+
{
116+
SetAsset(szAsset, szAssetText, true);
117+
}
118+
119+
void CDiscordRichPresence::SetAssetSmallData(const char* szAsset, const char* szAssetText)
120+
{
121+
SetAsset(szAsset, szAssetText, false);
122+
}
123+
124+
void CDiscordRichPresence::SetAsset(const char* szAsset, const char* szAssetText, bool isLarge)
125+
{
126+
if (isLarge)
127+
{
128+
m_strDiscordAppAsset = (szAsset && *szAsset) ? szAsset : DEFAULT_APP_ASSET;
129+
m_strDiscordAppAssetText = (szAssetText && *szAssetText) ? szAssetText : DEFAULT_APP_ASSET_TEXT;
130+
}
131+
else
132+
{
133+
m_strDiscordAppAssetSmall = (szAsset && *szAsset) ? szAsset : DEFAULT_APP_ASSET_SMALL;
134+
m_strDiscordAppAssetSmallText = (szAssetText && *szAssetText) ? szAssetText : DEFAULT_APP_ASSET_SMALL_TEXT;
135+
}
136+
m_bUpdateRichPresence = true;
137+
}
138+
139+
bool CDiscordRichPresence::SetPresenceState(const char* szState, bool bCustom)
140+
{
141+
if (bCustom)
142+
m_strDiscordAppCustomState = szState;
143+
else
144+
m_strDiscordAppState = szState;
145+
146+
m_bUpdateRichPresence = true;
147+
return true;
148+
}
149+
150+
bool CDiscordRichPresence::SetPresenceButtons(unsigned short int iIndex, const char* szName, const char* szUrl)
151+
{
152+
// Should it always return true?
153+
if (iIndex <= 2)
154+
{
155+
std::decay_t<decltype(*m_aButtons)> buttons;
156+
if (m_aButtons)
157+
buttons = *m_aButtons;
158+
159+
if (iIndex == 1)
160+
std::get<0>(buttons) = {szName, szUrl};
161+
else if (iIndex == 2)
162+
std::get<1>(buttons) = {szName, szUrl};
163+
164+
m_aButtons = buttons;
165+
m_bUpdateRichPresence = true;
166+
167+
return true;
168+
}
169+
170+
return false;
171+
}
172+
173+
bool CDiscordRichPresence::SetPresenceDetails(const char* szDetails, bool bCustom)
174+
{
175+
m_strDiscordAppDetails = szDetails;
176+
m_bUpdateRichPresence = true;
177+
return true;
178+
}
179+
180+
bool CDiscordRichPresence::ResetDiscordData()
181+
{
182+
SetDefaultData();
183+
184+
if (m_bDiscordRPCEnabled)
185+
{
186+
RestartDiscord();
187+
m_bUpdateRichPresence = true;
188+
}
189+
return true;
190+
}
191+
192+
bool CDiscordRichPresence::SetApplicationID(const char* szAppID)
193+
{
194+
m_strDiscordAppCurrentId = (szAppID && *szAppID) ? szAppID : DEFAULT_APP_ID;
195+
196+
if (m_bDiscordRPCEnabled)
197+
{
198+
RestartDiscord();
199+
m_bUpdateRichPresence = true;
200+
}
201+
return true;
202+
}
203+
204+
bool CDiscordRichPresence::SetDiscordRPCEnabled(bool bEnabled)
205+
{
206+
m_bDiscordRPCEnabled = bEnabled;
207+
208+
if (!bEnabled)
209+
{
210+
ShutdownDiscord();
211+
return true;
212+
}
213+
214+
InitializeDiscord();
215+
m_bUpdateRichPresence = true;
216+
return true;
217+
}
218+
219+
bool CDiscordRichPresence::IsDiscordRPCEnabled() const
220+
{
221+
return m_bDiscordRPCEnabled;
222+
}
223+
224+
bool CDiscordRichPresence::IsDiscordCustomDetailsDisallowed() const
225+
{
226+
return m_bDisallowCustomDetails;
227+
}

0 commit comments

Comments
 (0)