Implementing Discord RPC

From Valve Developer Community
Jump to: navigation, search

To do: This implementation is fairly basic, so any edits or additions are greatly appreciated!


Implementing Discord RPC into the Source engine is simple, and it makes your mod stand out more, as your in-game "presence" can be seen through Discord.

What is Discord RPC?

Discord RPC is a library for interfacing your game with a locally running Discord desktop client. It's known to work on Windows, macOS, and Linux. You can use the lib directly if you like, or use it as a guide to writing your own if it doesn't suit your game as is.


Below is an example using this article as a base, with obvious modifications:

DiscordRPCExample.png

The Installation

First off you'll need to download the latest version Discord RPC. You can do so by going to Discord's GitHub repository here. And download the latest discord-rpc-win.zip file, as of writing we'll be using v3.3.0. Once downloaded, open the archive and go to win32-dynamic/lib/ and extract discord-rpc.lib to your mod's <src code directory>/src/lib/public/ once that's done we'll do the same for the include files, in your archive take a step back and go into the include folder, now extract discord_rpc.h and discord_register.h to <src code directory>/src/public/. Now for the final step, once again in your archive take a step back and go into the bin folder, now extract discord-rpc.dll to your mod's /bin folder ex. <Steam directory>/steamapps/sourcemods/YourMod/bin/.

The Setup

Now that the installation is done, it's time to set up our system. There's not a whole lot to do here, but this is a necessary step you'll have to take to make the implementation work.

To start off, go into <src code directory>/src/vpc_scripts/ and open source_dll_win32_base.vpc with your favorite text editor (I personally use Notepad++). Once you've opened the file, scroll all the way down to $Folder "Link Libraries" and under $Implib "$LIBPUBLIC\vstdlib" add $Lib "$LIBPUBLIC\discord-rpc". After you've modified the VPC scripts, make sure to rerun createallprojects.bat and/or creategameprojects.bat before continuing, otherwise you might experience errors while building.

All that needs to be done now is to start the Discord RPC system when the modification launches. To do this, open up cdll_client_int.cpp, and at the bottom add #include "discord_rpc.h" and #include <time.h> to the list of includes, then under

static ConVar s_cl_class("cl_class", "default", FCVAR_USERINFO|FCVAR_ARCHIVE, "Default class when joining a game");

add

// Discord RPC
static ConVar cl_discord_appid("cl_discord_appid", "123456789123456789", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT);
static int64_t startTimestamp = time(0);

where the numbers after cl_discord_appid is your application's RPC AppID, to set up a RPC client go here, once your application is set up the number you'll want to place after cl_discord_appid is the "Client ID" seen in "General Information".

When that's done add the following above CHLClient::CHLClient()

//-----------------------------------------------------------------------------
// Discord RPC
//-----------------------------------------------------------------------------
static void HandleDiscordReady(const DiscordUser* connectedUser)
{
	DevMsg("Discord: Connected to user %s#%s - %s\n",
		connectedUser->username,
		connectedUser->discriminator,
		connectedUser->userId);
}

static void HandleDiscordDisconnected(int errcode, const char* message)
{
	DevMsg("Discord: Disconnected (%d: %s)\n", errcode, message);
}

static void HandleDiscordError(int errcode, const char* message)
{
	DevMsg("Discord: Error (%d: %s)\n", errcode, message);
}

static void HandleDiscordJoin(const char* secret)
{
	// Not implemented
}

static void HandleDiscordSpectate(const char* secret)
{
	// Not implemented
}

static void HandleDiscordJoinRequest(const DiscordUser* request)
{
	// Not implemented
}

Now scroll down to CHLClient::Init( ... ) and add the following above return true; at the bottom of the function.

// Discord RPC
DiscordEventHandlers handlers;
memset(&handlers, 0, sizeof(handlers));
	
handlers.ready = HandleDiscordReady;
handlers.disconnected = HandleDiscordDisconnected;
handlers.errored = HandleDiscordError;
handlers.joinGame = HandleDiscordJoin;
handlers.spectateGame = HandleDiscordSpectate;
handlers.joinRequest = HandleDiscordJoinRequest;

char appid[255];
sprintf(appid, "%d", engine->GetAppID());
Discord_Initialize(cl_discord_appid.GetString(), &handlers, 1, appid);

if (!g_bTextMode)
{
	DiscordRichPresence discordPresence;
	memset(&discordPresence, 0, sizeof(discordPresence));

	discordPresence.state = "In-Game";
	discordPresence.details = "Main Menu";
	discordPresence.startTimestamp = startTimestamp;
	discordPresence.largeImageKey = "ModImageHere";
	Discord_UpdatePresence(&discordPresence);
}

"ModImageHere" is an image key, remember that Discord developer page you were on earlier, where you set up your application? Go back there and to your application page, now go down to "Rich Presence" and click on "Art Assets" and click "Add Image(s)". Add an image you want for your mod and whatever you name that image is the name you replace "ModImageHere" with.

Now that that's done, let's go further down to the CHLClient::Shutdown( void ) function, here we'll add the Shutdown method, add the following below the </code>#endif</code> for WORKSHOP_IMPORT_ENABLED

// Discord RPC
Discord_Shutdown();

Now go further down to CHLClient::LevelInitPreEntity( ... ) and above the comment for g_RagdollLVManager.SetLowViolence( pMapName ); add

// Discord RPC
if (!g_bTextMode)
{
	DiscordRichPresence discordPresence;
	memset(&discordPresence, 0, sizeof(discordPresence));

	char buffer[256];
	discordPresence.state = "In-Game";
	sprintf(buffer, "Map: %s", pMapName);
	discordPresence.details = buffer;
	discordPresence.largeImageKey = "ModImageHere";
	Discord_UpdatePresence(&discordPresence);
}

Now we'll add the last piece of code into CHLClient::LevelShutdown( void ). Below

gHUD.LevelShutdown();

add

// Discord RPC
if (!g_bTextMode)
{
	DiscordRichPresence discordPresence;
	memset(&discordPresence, 0, sizeof(discordPresence));

	discordPresence.state = "In-Game";
	discordPresence.details = "Main Menu";
	discordPresence.startTimestamp = startTimestamp;
	discordPresence.largeImageKey = "ModImageHere";
	Discord_UpdatePresence(&discordPresence);
}

Conclusion

The installation and setup are both complete! Try launching your mod and check your Discord status, now load up a map and check again!

This implementation works on Source SDK 2013 (SP/MP), as well as Source SDK 2007 (thanks to the IOSoccer team for testing!).