Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discord Rich Presence support #3167

Merged
merged 31 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b7fc7e7
get draft from #2577
znvjder Aug 12, 2023
e2da0e0
get draft from #2577
znvjder Aug 12, 2023
b848629
add checkbox to allow connecting with discord-rpc
znvjder Aug 12, 2023
6dde169
safe delete only if used
znvjder Aug 12, 2023
317a2c5
fix vars when game is launching
znvjder Aug 13, 2023
943c479
add function to set appid and small fixes
znvjder Aug 13, 2023
aa83817
added functions to manage assets, small fixes and bugs
znvjder Aug 19, 2023
6eeaf89
rewriten setAsset, refactor littlebit
znvjder Aug 20, 2023
1d9ecf2
add function to check client connection with discord
znvjder Aug 27, 2023
9b20c67
add buttons to rpc
znvjder Aug 27, 2023
2c30ac4
minor amendments, renaming of functions
znvjder Aug 28, 2023
7c4ed8e
remove update discord-rpc from build
znvjder Aug 28, 2023
e85334e
thats too
znvjder Aug 28, 2023
1588eb4
Memory leak fixed, presence updated on app change
znvjder Sep 2, 2023
e22f704
Presence state change, change of time variable
znvjder Sep 2, 2023
e119295
Fixed unable to change custom state
znvjder Sep 2, 2023
4a0792c
Merge branch 'master' into feature/DiscordRPC
znvjder Sep 16, 2023
e8a590f
Merge branch 'master' into feature/DiscordRPC
znvjder Oct 9, 2023
438f1f8
Added const to IsDiscordRPCEnabled
znvjder Oct 9, 2023
2f1bfda
Added missing argument validation
znvjder Oct 9, 2023
a8f8b30
Exclude vendor files, added premake script
znvjder Oct 9, 2023
0d661d4
Re-run actions
Lpsd Oct 9, 2023
2b42cd9
Bump discord-rpc version
Lpsd Oct 9, 2023
5123192
Fix broken discord tag_name
znvjder Oct 9, 2023
467f1a0
Bump discord-rpc version
Lpsd Oct 9, 2023
d021e7f
Merge branch 'feature/DiscordRPC' of https://github.com/znvjder/mtasa…
Lpsd Oct 9, 2023
f253967
Revert to 2b42cd9
Lpsd Oct 9, 2023
85eab79
Bump discord-rpc version
Lpsd Oct 9, 2023
4a5d3ea
Disabled data customization if the app is not own
znvjder Oct 10, 2023
445a8e6
Change DEFAULT_APP_ID
Lpsd Oct 11, 2023
03d7797
Remove comment
Lpsd Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
222 changes: 222 additions & 0 deletions Client/core/CDiscordRichPresence.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*****************************************************************************
*
* 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[] = "1139666477813866546";
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[] = "";
Lpsd marked this conversation as resolved.
Show resolved Hide resolved

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

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;
}

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_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_bAllowCustomDetails)
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)
{
if (bCustom && !m_bAllowCustomDetails)
return false;

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()
{
return m_bDiscordRPCEnabled;
}
Loading