diff --git a/libs/discord-rpc/win32-dynamic/bin/discord-rpc.dll b/libs/discord-rpc/win32-dynamic/bin/discord-rpc.dll new file mode 100644 index 00000000..4254cfa2 Binary files /dev/null and b/libs/discord-rpc/win32-dynamic/bin/discord-rpc.dll differ diff --git a/libs/discord-rpc/win32-dynamic/include/discord_register.h b/libs/discord-rpc/win32-dynamic/include/discord_register.h new file mode 100644 index 00000000..4c16b68a --- /dev/null +++ b/libs/discord-rpc/win32-dynamic/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/libs/discord-rpc/win32-dynamic/include/discord_rpc.h b/libs/discord-rpc/win32-dynamic/include/discord_rpc.h new file mode 100644 index 00000000..3e1441e0 --- /dev/null +++ b/libs/discord-rpc/win32-dynamic/include/discord_rpc.h @@ -0,0 +1,87 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* request); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib b/libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib new file mode 100644 index 00000000..367920b3 Binary files /dev/null and b/libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 079bbba5..5805d2a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -220,6 +220,8 @@ set(SRB2_CONFIG_HAVE_ZLIB ON CACHE BOOL "Enable zlib support.") set(SRB2_CONFIG_HAVE_GME ON CACHE BOOL "Enable GME support.") +set(SRB2_CONFIG_HAVE_DISCORDRPC OFF CACHE BOOL + "Enable Discord rich presence support.") set(SRB2_CONFIG_HWRENDER ON CACHE BOOL "Enable hardware rendering through OpenGL.") set(SRB2_CONFIG_USEASM OFF CACHE BOOL @@ -325,6 +327,19 @@ if(${SRB2_CONFIG_HAVE_GME}) endif() endif() +# TO-DO: make this check for the dependency like the others.. +if(${SRB2_CONFIG_HAVE_DISCORDRPC}) + add_definitions(-DHAVE_DISCORDRPC) + set(SRB2_DISCORDRPC_SOURCES + discord.c + discord.h + ) + + prepend_sources(SRB2_DISCORDRPC_SOURCES) + + source_group("Discord RPC" FILES ${SRB2_DISCORDRPC_SOURCES}) +endif() + if(${SRB2_CONFIG_HAVE_ZLIB}) find_package(ZLIB) if(${ZLIB_FOUND}) diff --git a/src/Makefile b/src/Makefile index 85ecb49a..693de695 100644 --- a/src/Makefile +++ b/src/Makefile @@ -367,6 +367,13 @@ CFLAGS+=-DHAVE_MINIUPNPC endif endif +ifdef HAVE_DISCORDRPC +LIBS+=-ldiscord-rpc +CFLAGS+=-DHAVE_DISCORDRPC +OBJS+=$(OBJDIR)/discord.o +OBJS+=$(OBJDIR)/discord_pass.o +endif + ifndef NO_LUA include blua/Makefile.cfg endif diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f1f71417..9df160aa 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -55,6 +55,10 @@ #include "sdl12/SRB2XBOX/xboxhelp.h" #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // // NETWORKING // @@ -3258,6 +3262,10 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) #ifdef HAVE_BLUA LUAh_PlayerJoin(newplayernum); #endif + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } // Xcmd XD_REMOVEPLAYER @@ -3279,6 +3287,10 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) } CL_RemovePlayer(READUINT8(*p)); + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static boolean SV_AddWaitingPlayers(void) diff --git a/src/d_main.c b/src/d_main.c index e1bcce8c..a71f72be 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -102,6 +102,10 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "lua_script.h" #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // platform independant focus loss UINT8 window_notinfocus = false; @@ -1351,6 +1355,10 @@ void D_SRB2Main(void) CONS_Printf("ST_Init(): Init status bar.\n"); ST_Init(); +#ifdef HAVE_DISCORDRPC + DRPC_Init(); +#endif + if (M_CheckParm("-room")) { if (!M_IsNextParm()) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 120ffc55..12502d97 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -55,6 +55,10 @@ #define CV_RESTRICT 0 #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // ------ // protos // ------ @@ -1793,6 +1797,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } else SetPlayerSkinByNum(playernum, skin); + +#ifdef HAVE_DISCORDRPC + if (playernum == consoleplayer) + DRPC_UpdatePresence(); +#endif } void SendWeaponPref(void) @@ -2385,6 +2394,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) if (demorecording) // Okay, level loaded, character spawned and skinned, G_BeginRecording(); // I AM NOW READY TO RECORD. demo_start = true; + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static void Command_Pause(void) @@ -4281,6 +4294,10 @@ static void TimeLimit_OnChange(void) } else if (netgame || multiplayer) CONS_Printf(M_GetText("Time limit disabled\n")); + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } /** Adjusts certain settings to match a changed gametype. diff --git a/src/d_netfil.c b/src/d_netfil.c index 7927c4ec..47d4d276 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -475,7 +475,7 @@ static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) char wadfilename[MAX_WADPATH]; if (cv_noticedownload.value) - CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node)); + CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node, false)); // Find the last file in the list and set a pointer to its "next" field q = &transfer[node].txlist; diff --git a/src/discord.c b/src/discord.c new file mode 100644 index 00000000..12715cc8 --- /dev/null +++ b/src/discord.c @@ -0,0 +1,170 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2012-2018 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2012-2016 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file discord.h +/// \brief Discord Rich Presence handling + +#ifdef HAVE_DISCORDRPC + +#include "i_system.h" +#include "d_clisrv.h" +#include "d_netcmd.h" +#include "i_net.h" +#include "g_game.h" +#include "p_tick.h" +#include "m_menu.h" // gametype_cons_t +#include "r_things.h" // skins +#include "mserv.h" // ms_RoomId + +#include "discord.h" +#include "discord_pass.h" // .gitignore'd file for volitile information; DO NOT push this info +#include "doomdef.h" + +// +// DRPC_Handle's +// +static inline void DRPC_HandleReady(const DiscordUser *user) +{ + CONS_Printf("Discord: connected to %s#%s - %s\n", user->username, user->discriminator, user->userId); +} + +static inline void DRPC_HandleDisconnect(int err, const char *msg) +{ + CONS_Printf("Discord: disconnected (%d: %s)\n", err, msg); +} + +static inline void DRPC_HandleError(int err, const char *msg) +{ + CONS_Printf("Discord: error (%d, %s)\n", err, msg); +} + +static inline void DRPC_HandleJoin(const char *secret) +{ + CONS_Printf("Discord: connecting to %s\n", secret); + COM_BufAddText(va("connect \"%s\"\n", secret)); +} + +// +// DRPC_Init: starting up the handles, call Discord_initalize +// +void DRPC_Init(void) +{ + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + + if (!discordappid) + return; + + handlers.ready = DRPC_HandleReady; + handlers.disconnected = DRPC_HandleDisconnect; + handlers.errored = DRPC_HandleError; + handlers.joinGame = DRPC_HandleJoin; + + Discord_Initialize(discordappid, &handlers, 1, NULL); + I_AddExitFunc(Discord_Shutdown); + DRPC_UpdatePresence(); +} + +// +// DRPC_UpdatePresence: Called whenever anything changes about server info +// +void DRPC_UpdatePresence(void) +{ + char mapimg[8]; + char mapname[48]; + char charimg[21]; + char charname[28]; + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + + if (discordappid) + { + // Server info + if (netgame) + { + const char *address; + + switch (ms_RoomId) + { + case -1: discordPresence.state = "Private"; break; // Private server + case 33: discordPresence.state = "Standard"; break; + case 28: discordPresence.state = "Casual"; break; + default: discordPresence.state = "???"; break; // How? + } + + discordPresence.partyId = "1"; // We don't really have "party" IDs, so to make invites expire we just let it reset to 0 outside of servers + + // Grab the host's IP for joining. + if (I_GetNodeAddress && (address = I_GetNodeAddress(servernode, true)) != NULL) + { + discordPresence.joinSecret = address; + CONS_Printf("%s\n", address); + } + + discordPresence.partySize = D_NumPlayers(); // Players in server + discordPresence.partyMax = cv_maxplayers.value; // Max players + } + else + discordPresence.state = "Offline"; + + // Gametype info + if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING) + { + if (modeattacking) + discordPresence.details = "Record Attack"; + else + discordPresence.details = gametype_cons_t[gametype].strvalue; + } + + // Map info + if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) + { + snprintf(mapimg, 8, "%s", G_BuildMapName(gamemap)); + strlwr(mapimg); + + discordPresence.largeImageKey = mapimg; // Map image + + if (mapheaderinfo[gamemap-1]->lvlttl[0] != '\0') + snprintf(mapname, 48, "Map: %s%s%s", + mapheaderinfo[gamemap-1]->lvlttl, + (strlen(mapheaderinfo[gamemap-1]->zonttl) > 0) ? va(" %s",mapheaderinfo[gamemap-1]->zonttl) : // SRB2kart + ((mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " ZONE"), + (strlen(mapheaderinfo[gamemap-1]->actnum) > 0) ? va(" %s",mapheaderinfo[gamemap-1]->actnum) : ""); + else + snprintf(mapname, 48, "???"); + + discordPresence.largeImageText = mapname; // Map name + + //if (cv_timelimit.value) + //discordPresence.endTimestamp = levelstarttime + (cv_timelimit.value*60); // Time limit if applicable + } + else if (gamestate == GS_VOTING) + { + discordPresence.largeImageText = "Voting"; + } + + // Player info + if (playeringame[consoleplayer]) + { + //discordPresence.startTimestamp = levelstarttime; // Time in level + if (!players[consoleplayer].spectator) + { + snprintf(charimg, 21, "char%s", skins[players[consoleplayer].skin].name); + discordPresence.smallImageKey = charimg; // Character image + + snprintf(charname, 28, "Character: %s", skins[players[consoleplayer].skin].realname); + discordPresence.smallImageText = charname; // Character name + } + } + } + + Discord_UpdatePresence(&discordPresence); +} + +#endif diff --git a/src/discord.h b/src/discord.h new file mode 100644 index 00000000..f9873d25 --- /dev/null +++ b/src/discord.h @@ -0,0 +1,20 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2012-2018 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2012-2016 by Sonic Team Junior. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file discord.h +/// \brief Discord Rich Presence handling + +#ifdef HAVE_DISCORDRPC + +#include "discord_rpc.h" + +void DRPC_Init(void); +void DRPC_UpdatePresence(void); + +#endif diff --git a/src/discord_pass.c b/src/discord_pass.c new file mode 100644 index 00000000..e69de29b diff --git a/src/discord_pass.h b/src/discord_pass.h new file mode 100644 index 00000000..4adeda90 --- /dev/null +++ b/src/discord_pass.h @@ -0,0 +1,3 @@ +// This file is .gitignore'd for a reason! This is very sensitive info! +// Do not push any change that makes this a valid app ID! +const char* discordappid = "503531144395096085"; diff --git a/src/g_game.c b/src/g_game.c index 617712f7..9b495a80 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -47,6 +47,10 @@ #include "md5.h" // demo checksums #include "k_kart.h" // SRB2kart +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; @@ -2252,6 +2256,10 @@ void G_Ticker(boolean run) if (spectatedelay4) spectatedelay4--; } + +#ifdef HAVE_DISCORDRPC + Discord_RunCallbacks(); +#endif } // @@ -6347,6 +6355,9 @@ boolean G_CheckDemoStatus(void) void G_SetGamestate(gamestate_t newstate) { gamestate = newstate; +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } /* These functions handle the exitgame flag. Before, when the user diff --git a/src/mserv.c b/src/mserv.c index 98849df4..5a99fe14 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -97,6 +97,10 @@ #include "i_addrinfo.h" +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // ================================ DEFINITIONS =============================== #define PACKET_SIZE 1024 @@ -845,6 +849,9 @@ void RegisterServer(void) MSOpenUDPSocket(); // keep the TCP connection open until AddToMasterServer() is completed; +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static inline void SendPingToMasterServer(void) @@ -896,7 +903,7 @@ void SendAskInfoViaMS(INT32 node, tic_t asktime) // This must be called after calling MSOpenUDPSocket, due to the // static buffer. - address = I_GetNodeAddress(node); + address = I_GetNodeAddress(node, false); // no address? if (!address) @@ -946,6 +953,10 @@ void UnregisterServer(void) CloseConnection(); MSCloseUDPSocket(); MSLastPing = 0; + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } void MasterClient_Ticker(void) diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 871de392..eb735382 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -2850,11 +2850,11 @@ void I_Error(const char *error, ...) I_ShutdownGraphics(); if (errorcount == 6) I_ShutdownInput(); - if (errorcount == 7) - I_ShutdownSystem(); if (errorcount == 8) - SDL_Quit(); + I_ShutdownSystem(); if (errorcount == 9) + SDL_Quit(); + if (errorcount == 10) { M_SaveConfig(NULL); G_SaveGameData(false); diff --git a/src/win32/Makefile.cfg b/src/win32/Makefile.cfg index da66e996..e3dfa3d6 100644 --- a/src/win32/Makefile.cfg +++ b/src/win32/Makefile.cfg @@ -25,6 +25,8 @@ endif HAVE_MINIUPNPC=1 + HAVE_DISCORDRPC=1 + OPTS=-DSTDC_HEADERS ifndef GCC44 @@ -133,3 +135,9 @@ else LDFLAGS+=-L../libs/miniupnpc/mingw32 endif #MINGW64 endif + +ifdef HAVE_DISCORDRPC + CPPFLAGS+=-I../libs/discord-rpc/win32-dynamic/include + LDFLAGS+=-L../libs/discord-rpc/win32-dynamic/lib + LIBS+=-ldiscord-rpc +endif