Merge branch 'discord-rpc-support' into 'next'

Discord Rich Presence

See merge request KartKrew/Kart-Public!207
This commit is contained in:
Sal 2020-08-30 21:48:44 -04:00
commit 1aca163d12
38 changed files with 1751 additions and 90 deletions

View file

@ -0,0 +1,23 @@
include(LibFindMacros)
libfind_pkg_check_modules(DISCORDRPC_PKGCONF DISCORDRPC)
find_path(DISCORDRPC_INCLUDE_DIR
NAMES discord_rpc.h
PATHS
${DISCORDRPC_PKGCONF_INCLUDE_DIRS}
"/usr/include"
"/usr/local/include"
)
find_library(DISCORDRPC_LIBRARY
NAMES discord-rpc
PATHS
${DISCORDRPC_PKGCONF_LIBRARY_DIRS}
"/usr/lib"
"/usr/local/lib"
)
set(DISCORDRPC_PROCESS_INCLUDES DISCORDRPC_INCLUDE_DIR)
set(DISCORDRPC_PROCESS_LIBS DISCORDRPC_LIBRARY)
libfind_process(DISCORDRPC)

View file

@ -2,10 +2,7 @@ tar-ignore = "assets/*.srb"
tar-ignore = "assets/*.pk3"
tar-ignore = "assets/*.dta"
tar-ignore = "assets/*.wad"
<<<<<<< HEAD:debian-template/source/options
tar-ignore = "assets/*.kart"
=======
>>>>>>> e251f9c230beda984cdcdea7e903d765f1c68f6f:debian-template/source/options
tar-ignore = "assets/debian/${PACKAGE_NAME}-data/*"
tar-ignore = "assets/debian/tmp/*"
tar-ignore = "*.obj"

View file

@ -1,6 +1,6 @@
# SRB2Kart - Which DLLs do I need to bundle?
Updated 12/4/2018 (v2.1.21)
Updated 8/23/2020 (v1.3)
Here are the required DLLs, per build. For each architecture, copy all the binaries from these folders:
@ -14,6 +14,7 @@ and don't forget to build r_opengl.dll for srb2dd.
* libs\dll-binaries\i686\exchndl.dll
* libs\dll-binaries\i686\libgme.dll
* libs\dll-binaries\i686\discord-rpc.dll
* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll)
* libs\SDL2\i686-w64-mingw32\bin\SDL2.dll
* libs\SDL2_mixer\i686-w64-mingw32\bin\*.dll (get everything)
@ -22,22 +23,7 @@ and don't forget to build r_opengl.dll for srb2dd.
* libs\dll-binaries\x86_64\exchndl.dll
* libs\dll-binaries\x86_64\libgme.dll
* libs\dll-binaries\x86_64\discord-rpc.dll
* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll)
* libs\SDL2\x86_64-w64-mingw32\bin\SDL2.dll
* libs\SDL2_mixer\x86_64-w64-mingw32\bin\*.dll (get everything)
## srb2kartdd, 32-bit
* libs\dll-binaries\i686\exchndl.dll
* libs\dll-binaries\i686\fmodex.dll
* libs\dll-binaries\i686\libgme.dll
* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll)
* r_opengl.dll (build this from make)
## srb2kartdd, 64-bit
* libs\dll-binaries\x86_64\exchndl.dll
* libs\dll-binaries\x86_64\fmodex.dll
* libs\dll-binaries\x86_64\libgme.dll
* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll)
* r_opengl.dll (build this from make)

16
libs/discord-rpc.props Normal file
View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<LibraryPath Condition="'$(Platform)' == 'Win32'">$(SolutionDir)libs\discord-rpc\win32-dynamic\lib;$(LibraryPath)</LibraryPath>
<IncludePath Condition="'$(Platform)' == 'Win32'">$(SolutionDir)libs\discord-rpc\win32-dynamic\lib;$(IncludePath)</IncludePath>
<LibraryPath Condition="'$(Platform)' == 'x64'">$(SolutionDir)libs\discord-rpc\win64-dynamic\lib;$(LibraryPath)</LibraryPath>
<IncludePath Condition="'$(Platform)' == 'x64'">$(SolutionDir)libs\discord-rpc\win64-dynamic\lib;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Platform)' == 'Win32' OR '$(Platform)' == 'x64'">
<Link>
<AdditionalDependencies>discord-rpc.dll.a;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>

View file

@ -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

View file

@ -0,0 +1,87 @@
#pragma once
#include <stdint.h>
// 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

Binary file not shown.

View file

@ -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

View file

@ -0,0 +1,87 @@
#pragma once
#include <stdint.h>
// 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -221,6 +221,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_HAVE_CURL ON CACHE BOOL
"Enable curl support, used for downloading files via HTTP.")
set(SRB2_CONFIG_HWRENDER ON CACHE BOOL
@ -235,7 +237,7 @@ set(SRB2_CONFIG_STATIC_OPENGL OFF CACHE BOOL
### use internal libraries?
if(${CMAKE_SYSTEM} MATCHES "Windows") ###set on Windows only
set(SRB2_CONFIG_USE_INTERNAL_LIBRARIES OFF CACHE BOOL
"Use SRB2's internal copies of required dependencies (SDL2, PNG, zlib, GME).")
"Use SRB2Kart's internal copies of required dependencies (SDL2, PNG, zlib, GME).")
endif()
if(${SRB2_CONFIG_HAVE_BLUA})
@ -330,7 +332,7 @@ if(${SRB2_CONFIG_HAVE_GME})
if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
set(GME_FOUND ON)
set(GME_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/gme/include)
if(${SRB2_SYSTEM_BITS} EQUAL 64)
if(${SRB2_SYSTEM_BITS} EQUAL 64)
set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win64 -lgme")
else() # 32-bit
set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win32 -lgme")
@ -346,6 +348,32 @@ if(${SRB2_CONFIG_HAVE_GME})
endif()
endif()
if(${SRB2_CONFIG_HAVE_DISCORDRPC})
if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
set(DISCORDRPC_FOUND ON)
if(${SRB2_SYSTEM_BITS} EQUAL 64)
set(DISCORDRPC_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/discord-rpc/win64-dynamic/include)
set(DISCORDRPC_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/discord-rpc/win64-dynamic/lib -ldiscord-rpc")
else() # 32-bit
set(DISCORDRPC_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/discord-rpc/win32-dynamic/include)
set(DISCORDRPC_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/discord-rpc/win32-dynamic/lib -ldiscord-rpc")
endif()
else()
find_package(DiscordRPC)
endif()
if(${DISCORDRPC_FOUND})
set(SRB2_HAVE_DISCORDRPC ON)
add_definitions(-DHAVE_DISCORDRPC)
set(SRB2_DISCORDRPC_SOURCES discord.c)
set(SRB2_DISCORDRPC_HEADERS discord.h)
prepend_sources(SRB2_DISCORDRPC_SOURCES)
prepend_sources(SRB2_DISCORDRPC_HEADERS)
source_group("Discord Rich Presence" FILES ${SRB2_DISCORDRPC_SOURCES} ${SRB2_DISCORDRPC_HEADERS})
else()
message(WARNING "You have specified that Discord Rich Presence is available but it was not found.")
endif()
endif()
if(${SRB2_CONFIG_HAVE_ZLIB})
if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
set(ZLIB_FOUND ON)

View file

@ -385,6 +385,12 @@ CFLAGS+=-DHAVE_MINIUPNPC
endif
endif
ifdef HAVE_DISCORDRPC
LIBS+=-ldiscord-rpc
CFLAGS+=-DHAVE_DISCORDRPC
OBJS+=$(OBJDIR)/discord.o
endif
ifndef NO_LUA
include blua/Makefile.cfg
endif

View file

@ -46,6 +46,7 @@
#include "lua_script.h"
#include "lua_hook.h"
#include "k_kart.h"
#include "s_sound.h" // sfx_syfail
#ifdef CLIENT_LOADINGSCREEN
// cl loading screen
@ -57,6 +58,10 @@
#include "sdl12/SRB2XBOX/xboxhelp.h"
#endif
#ifdef HAVE_DISCORDRPC
#include "discord.h"
#endif
//
// NETWORKING
//
@ -1457,7 +1462,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl, mapheaderinfo[gamemap-1]->actnum) < 0)
{
// If there's an encoding error, send UNKNOWN, we accept that the above may be truncated
strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", 33);
}
}
else
@ -1468,13 +1473,13 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl) < 0)
{
// If there's an encoding error, send UNKNOWN, we accept that the above may be truncated
strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", 33);
}
}
}
}
else
strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", 33);
netbuffer->u.serverinfo.maptitle[32] = '\0';
@ -1601,6 +1606,15 @@ static boolean SV_SendServerConfig(INT32 node)
netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor;
}
netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value));
netbuffer->u.servercfg.allownewplayer = cv_allownewplayer.value;
#ifdef HAVE_DISCORDRPC
netbuffer->u.servercfg.discordinvites = (boolean)cv_discordinvites.value;
#else
netbuffer->u.servercfg.discordinvites = false;
#endif
memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
op = p = netbuffer->u.servercfg.varlengthinputs;
@ -1798,7 +1812,7 @@ static void CL_LoadReceivedSavegame(void)
if (strlen(mapheaderinfo[gamemap-1]->zonttl) > 0)
CON_LogMessage(va(" %s", mapheaderinfo[gamemap-1]->zonttl));
else if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
CON_LogMessage(M_GetText(" ZONE"));
CON_LogMessage(M_GetText(" Zone"));
if (strlen(mapheaderinfo[gamemap-1]->actnum) > 0)
CON_LogMessage(va(" %s", mapheaderinfo[gamemap-1]->actnum));
}
@ -3349,6 +3363,11 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
#endif
}
if (msg == KICK_MSG_PLAYER_QUIT)
S_StartSound(NULL, sfx_leave); // intended leave
else
S_StartSound(NULL, sfx_syfail); // he he he
switch (msg)
{
case KICK_MSG_GO_AWAY:
@ -3476,12 +3495,17 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
consvar_t cv_netticbuffer = {"netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL };
static void Joinable_OnChange(void);
consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_CALL, CV_OnOff, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL};
#ifdef VANILLAJOINNEXTROUND
consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
#endif
static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}};
consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE|CV_CALL, maxplayers_cons_t, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL};
static CV_PossibleValue_t resynchattempts_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
consvar_t cv_resynchattempts = {"resynchattempts", "5", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL };
@ -3498,6 +3522,15 @@ consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons
static void Got_AddPlayer(UINT8 **p, INT32 playernum);
static void Got_RemovePlayer(UINT8 **p, INT32 playernum);
static void Joinable_OnChange(void)
{
#ifdef HAVE_DISCORDRPC
DRPC_SendDiscordInfo();
#else
return;
#endif
}
// called one time at init
void D_ClientServerInit(void)
{
@ -3749,6 +3782,9 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
if (netgame)
{
if (node != mynode)
S_StartSound(NULL, sfx_join);
if (server && cv_showjoinaddress.value)
{
const char *address;
@ -3765,6 +3801,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
@ -3791,6 +3831,10 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum)
reason = READUINT8(*p);
CL_RemovePlayer(pnum, reason);
#ifdef HAVE_DISCORDRPC
DRPC_UpdatePresence();
#endif
}
static boolean SV_AddWaitingPlayers(void)
@ -4284,6 +4328,12 @@ static void HandlePacketFromAwayNode(SINT8 node)
memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
}
#ifdef HAVE_DISCORDRPC
discordInfo.maxPlayers = netbuffer->u.servercfg.maxplayer;
discordInfo.joinsAllowed = netbuffer->u.servercfg.allownewplayer;
discordInfo.everyoneCanInvite = netbuffer->u.servercfg.discordinvites;
#endif
nodeingame[(UINT8)servernode] = true;
serverplayer = netbuffer->u.servercfg.serverplayer;
doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);

View file

@ -334,6 +334,11 @@ typedef struct
char server_context[8]; // Unique context id, generated at server startup.
// Discord info (always defined for net compatibility)
UINT8 maxplayer;
boolean allownewplayer;
boolean discordinvites;
UINT8 varlengthinputs[0]; // Playernames and netvars
} ATTRPACK serverconfig_pak;

View file

@ -103,6 +103,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;
@ -727,6 +731,10 @@ void D_SRB2Loop(void)
#ifdef HAVE_BLUA
LUA_Step();
#endif
#ifdef HAVE_DISCORDRPC
Discord_RunCallbacks();
#endif
}
}
@ -841,9 +849,23 @@ static inline void D_CleanFile(char **filearray)
// Identify the SRB2 version, and IWAD file to use.
// ==========================================================================
static boolean AddIWAD(void)
{
char * path = va(pandf,srb2path,"srb2.srb");
if (FIL_ReadFileOK(path))
{
D_AddFile(path, startupwadfiles);
return true;
}
else
{
return false;
}
}
static void IdentifyVersion(void)
{
char *srb2wad1, *srb2wad2;
const char *srb2waddir = NULL;
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
@ -867,43 +889,28 @@ static void IdentifyVersion(void)
#ifdef _arch_dreamcast
srb2waddir = "/cd";
#else
srb2waddir = ".";
srb2waddir = srb2path;
#endif
}
}
#if defined (macintosh) && !defined (HAVE_SDL)
// cwd is always "/" when app is dbl-clicked
if (!stricmp(srb2waddir, "/"))
srb2waddir = I_GetWadDir();
#endif
// Commercial.
srb2wad1 = malloc(strlen(srb2waddir)+1+8+1);
srb2wad2 = malloc(strlen(srb2waddir)+1+8+1);
if (srb2wad1 == NULL && srb2wad2 == NULL)
I_Error("No more free memory to look in %s", srb2waddir);
if (srb2wad1 != NULL)
sprintf(srb2wad1, pandf, srb2waddir, "srb2.srb");
if (srb2wad2 != NULL)
sprintf(srb2wad2, pandf, srb2waddir, "srb2.wad");
// Load the IWAD
if (AddIWAD())
{
I_SaveCurrentWadDirectory();
}
else
{
if (!( I_UseSavedWadDirectory() && AddIWAD() ))
{
I_Error("SRB2.SRB not found! Expected in %s\n", srb2waddir);
}
}
// will be overwritten in case of -cdrom or unix/win home
snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir);
configfile[sizeof configfile - 1] = '\0';
// Load the IWAD
if (srb2wad2 != NULL && FIL_ReadFileOK(srb2wad2))
D_AddFile(srb2wad2, startupwadfiles);
else if (srb2wad1 != NULL && FIL_ReadFileOK(srb2wad1))
D_AddFile(srb2wad1, startupwadfiles);
else
I_Error("SRB2.SRB/SRB2.WAD not found! Expected in %s, ss files: %s or %s\n", srb2waddir, srb2wad1, srb2wad2);
if (srb2wad1)
free(srb2wad1);
if (srb2wad2)
free(srb2wad2);
// if you change the ordering of this or add/remove a file, be sure to update the md5
// checking in D_SRB2Main
@ -1610,6 +1617,10 @@ void D_SRB2Main(void)
if (!P_SetupLevel(false))
I_Quit(); // fail so reset game stuff
}
#ifdef HAVE_DISCORDRPC
DRPC_Init();
#endif
}
const char *D_Home(void)

View file

@ -55,6 +55,10 @@
#define CV_RESTRICT 0
#endif
#ifdef HAVE_DISCORDRPC
#include "discord.h"
#endif
// ------
// protos
// ------
@ -77,6 +81,7 @@ static void Got_RandomSeed(UINT8 **cp, INT32 playernum);
static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum);
static void Got_Teamchange(UINT8 **cp, INT32 playernum);
static void Got_Clearscores(UINT8 **cp, INT32 playernum);
static void Got_DiscordInfo(UINT8 **cp, INT32 playernum);
static void PointLimit_OnChange(void);
static void TimeLimit_OnChange(void);
@ -704,10 +709,12 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_showping);
#ifdef SEENAMES
CV_RegisterVar(&cv_allowseenames);
CV_RegisterVar(&cv_allowseenames);
#endif
CV_RegisterVar(&cv_dummyconsvar);
RegisterNetXCmd(XD_DISCORD, Got_DiscordInfo);
}
// =========================================================================
@ -1000,6 +1007,13 @@ void D_RegisterClientCommands(void)
#if defined(HAVE_BLUA) && defined(LUA_ALLOW_BYTECODE)
COM_AddCommand("dumplua", Command_Dumplua_f);
#endif
#ifdef HAVE_DISCORDRPC
CV_RegisterVar(&cv_discordrp);
CV_RegisterVar(&cv_discordstreamer);
CV_RegisterVar(&cv_discordasks);
CV_RegisterVar(&cv_discordinvites);
#endif
}
/** Checks if a name (as received from another player) is okay.
@ -1859,6 +1873,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)
@ -2670,6 +2689,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
if (demo.recording) // Okay, level loaded, character spawned and skinned,
G_BeginRecording(); // I AM NOW READY TO RECORD.
demo.deferstart = true;
#ifdef HAVE_DISCORDRPC
DRPC_UpdatePresence();
#endif
}
static void Command_Pause(void)
@ -4643,6 +4666,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.
@ -5668,3 +5695,32 @@ static void KartEliminateLast_OnChange(void)
if (G_RaceGametype() && cv_karteliminatelast.value)
P_CheckRacers();
}
void Got_DiscordInfo(UINT8 **p, INT32 playernum)
{
if (playernum != serverplayer /*&& !IsPlayerAdmin(playernum)*/)
{
// protect against hacked/buggy client
CONS_Alert(CONS_WARNING, M_GetText("Illegal Discord info command received from %s\n"), player_names[playernum]);
if (server)
{
XBOXSTATIC UINT8 buf[2];
buf[0] = (UINT8)playernum;
buf[1] = KICK_MSG_CON_FAIL;
SendNetXCmd(XD_KICK, &buf, 2);
}
return;
}
// Don't do anything with the information if we don't have Discord RP support
#ifdef HAVE_DISCORDRPC
discordInfo.maxPlayers = READUINT8(*p);
discordInfo.joinsAllowed = (boolean)READUINT8(*p);
discordInfo.everyoneCanInvite = (boolean)READUINT8(*p);
DRPC_UpdatePresence();
#else
(*p) += 3;
#endif
}

View file

@ -178,9 +178,10 @@ typedef enum
XD_MODIFYVOTE, // 23
XD_PICKVOTE, // 24
XD_REMOVEPLAYER,// 25
XD_DISCORD, // 26
#ifdef HAVE_BLUA
XD_LUACMD, // 26
XD_LUAVAR, // 27
XD_LUACMD, // 27
XD_LUAVAR, // 28
#endif
MAXNETXCMD
} netxcmd_t;

View file

@ -925,6 +925,18 @@ static void readlevelheader(MYFILE *f, INT32 num)
sizeof(mapheaderinfo[num-1]->subttl), va("Level header %d: subtitle", num));
continue;
}
else if (fastcmp(word, "LEVELNAME"))
{
deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
continue;
}
else if (fastcmp(word, "ZONETITLE"))
{
deh_strlcpy(mapheaderinfo[num-1]->zonttl, word2,
sizeof(mapheaderinfo[num-1]->zonttl), va("Level header %d: zonetitle", num));
continue;
}
// Lua custom options also go above, contents may be case sensitive.
if (fastncmp(word, "LUA.", 4))
@ -987,16 +999,6 @@ static void readlevelheader(MYFILE *f, INT32 num)
}
// Strings that can be truncated
else if (fastcmp(word, "LEVELNAME"))
{
deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
}
else if (fastcmp(word, "ZONETITLE"))
{
deh_strlcpy(mapheaderinfo[num-1]->zonttl, word2,
sizeof(mapheaderinfo[num-1]->zonttl), va("Level header %d: zonetitle", num));
}
else if (fastcmp(word, "SCRIPTNAME"))
{
deh_strlcpy(mapheaderinfo[num-1]->scriptname, word2,

734
src/discord.c Normal file
View file

@ -0,0 +1,734 @@
// SONIC ROBO BLAST 2 KART
//-----------------------------------------------------------------------------
// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour.
// Copyright (C) 2018-2020 by Kart Krew.
//
// 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
#ifdef HAVE_CURL
#include <curl/curl.h>
#endif // HAVE_CURL
#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 "z_zone.h"
#include "byteptr.h"
#include "discord.h"
#include "doomdef.h"
// Feel free to provide your own, if you care enough to create another Discord app for this :P
#define DISCORD_APPID "503531144395096085"
// length of IP strings
#define IP_SIZE 16
consvar_t cv_discordrp = {"discordrp", "On", CV_SAVE|CV_CALL, CV_OnOff, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_discordstreamer = {"discordstreamer", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_discordasks = {"discordasks", "Yes", CV_SAVE|CV_CALL, CV_YesNo, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL};
static CV_PossibleValue_t discordinvites_cons_t[] = {{0, "Admins Only"}, {1, "Everyone"}, {0, NULL}};
consvar_t cv_discordinvites = {"discordinvites", "Everyone", CV_SAVE|CV_CALL, discordinvites_cons_t, DRPC_SendDiscordInfo, 0, NULL, NULL, 0, 0, NULL};
struct discordInfo_s discordInfo;
discordRequest_t *discordRequestList = NULL;
#ifdef HAVE_CURL
struct SelfIPbuffer
{
CURL *curl;
char *pointer;
size_t length;
};
static char self_ip[IP_SIZE];
#endif // HAVE_CURL
/*--------------------------------------------------
static char *DRPC_XORIPString(const char *input)
Simple XOR encryption/decryption. Not complex or
very secretive because we aren't sending anything
that isn't easily accessible via our Master Server anyway.
--------------------------------------------------*/
static char *DRPC_XORIPString(const char *input)
{
const UINT8 xor[IP_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
char *output = malloc(sizeof(char) * (IP_SIZE+1));
UINT8 i;
for (i = 0; i < IP_SIZE; i++)
{
char xorinput;
if (!input[i])
break;
xorinput = input[i] ^ xor[i];
if (xorinput < 32 || xorinput > 126)
{
xorinput = input[i];
}
output[i] = xorinput;
}
output[i] = '\0';
return output;
}
/*--------------------------------------------------
static void DRPC_HandleReady(const DiscordUser *user)
Callback function, ran when the game connects to Discord.
Input Arguments:-
user - Struct containing Discord user info.
Return:-
None
--------------------------------------------------*/
static void DRPC_HandleReady(const DiscordUser *user)
{
if (cv_discordstreamer.value)
{
CONS_Printf("Discord: connected to %s\n", user->username);
}
else
{
CONS_Printf("Discord: connected to %s#%s (%s)\n", user->username, user->discriminator, user->userId);
}
}
/*--------------------------------------------------
static void DRPC_HandleDisconnect(int err, const char *msg)
Callback function, ran when disconnecting from Discord.
Input Arguments:-
err - Error type
msg - Error message
Return:-
None
--------------------------------------------------*/
static void DRPC_HandleDisconnect(int err, const char *msg)
{
CONS_Printf("Discord: disconnected (%d: %s)\n", err, msg);
}
/*--------------------------------------------------
static void DRPC_HandleError(int err, const char *msg)
Callback function, ran when Discord outputs an error.
Input Arguments:-
err - Error type
msg - Error message
Return:-
None
--------------------------------------------------*/
static void DRPC_HandleError(int err, const char *msg)
{
CONS_Alert(CONS_WARNING, "Discord error (%d: %s)\n", err, msg);
}
/*--------------------------------------------------
static void DRPC_HandleJoin(const char *secret)
Callback function, ran when Discord wants to
connect a player to the game via a channel invite
or a join request.
Input Arguments:-
secret - Value that links you to the server.
Return:-
None
--------------------------------------------------*/
static void DRPC_HandleJoin(const char *secret)
{
char *ip = DRPC_XORIPString(secret);
CONS_Printf("Connecting to %s via Discord\n", ip);
COM_BufAddText(va("connect \"%s\"\n", ip));
free(ip);
}
/*--------------------------------------------------
static boolean DRPC_InvitesAreAllowed(void)
Determines whenever or not invites or
ask to join requests are allowed.
Input Arguments:-
None
Return:-
true if invites are allowed, false otherwise.
--------------------------------------------------*/
static boolean DRPC_InvitesAreAllowed(void)
{
if (!Playing())
{
// We're not playing, so we should not be getting invites.
return false;
}
if (cv_discordasks.value == 0)
{
// Client has the CVar set to off, so never allow invites from this client.
return false;
}
if (discordInfo.joinsAllowed == true)
{
if (discordInfo.everyoneCanInvite == true)
{
// Everyone's allowed!
return true;
}
else if (consoleplayer == serverplayer || IsPlayerAdmin(consoleplayer))
{
// Only admins are allowed!
return true;
}
}
// Did not pass any of the checks
return false;
}
/*--------------------------------------------------
static void DRPC_HandleJoinRequest(const DiscordUser *requestUser)
Callback function, ran when Discord wants to
ask the player if another Discord user can join
or not.
Input Arguments:-
requestUser - DiscordUser struct for the user trying to connect.
Return:-
None
--------------------------------------------------*/
static void DRPC_HandleJoinRequest(const DiscordUser *requestUser)
{
discordRequest_t *append = discordRequestList;
discordRequest_t *newRequest;
if (DRPC_InvitesAreAllowed() == false)
{
// Something weird happened if this occurred...
Discord_Respond(requestUser->userId, DISCORD_REPLY_IGNORE);
return;
}
newRequest = Z_Calloc(sizeof(discordRequest_t), PU_STATIC, NULL);
newRequest->username = Z_Calloc(344, PU_STATIC, NULL);
snprintf(newRequest->username, 344, "%s", requestUser->username);
newRequest->discriminator = Z_Calloc(8, PU_STATIC, NULL);
snprintf(newRequest->discriminator, 8, "%s", requestUser->discriminator);
newRequest->userID = Z_Calloc(32, PU_STATIC, NULL);
snprintf(newRequest->userID, 32, "%s", requestUser->userId);
if (append != NULL)
{
discordRequest_t *prev = NULL;
while (append != NULL)
{
// CHECK FOR DUPES!! Ignore any that already exist from the same user.
if (!strcmp(newRequest->userID, append->userID))
{
Discord_Respond(newRequest->userID, DISCORD_REPLY_IGNORE);
DRPC_RemoveRequest(newRequest);
return;
}
prev = append;
append = append->next;
}
newRequest->prev = prev;
prev->next = newRequest;
}
else
{
discordRequestList = newRequest;
M_RefreshPauseMenu();
}
// Made it to the end, request was valid, so play the request sound :)
S_StartSound(NULL, sfx_requst);
}
/*--------------------------------------------------
void DRPC_RemoveRequest(discordRequest_t *removeRequest)
See header file for description.
--------------------------------------------------*/
void DRPC_RemoveRequest(discordRequest_t *removeRequest)
{
if (removeRequest->prev != NULL)
{
removeRequest->prev->next = removeRequest->next;
}
if (removeRequest->next != NULL)
{
removeRequest->next->prev = removeRequest->prev;
if (removeRequest == discordRequestList)
{
discordRequestList = removeRequest->next;
}
}
else
{
if (removeRequest == discordRequestList)
{
discordRequestList = NULL;
}
}
Z_Free(removeRequest->username);
Z_Free(removeRequest->userID);
Z_Free(removeRequest);
}
/*--------------------------------------------------
void DRPC_Init(void)
See header file for description.
--------------------------------------------------*/
void DRPC_Init(void)
{
DiscordEventHandlers handlers;
memset(&handlers, 0, sizeof(handlers));
handlers.ready = DRPC_HandleReady;
handlers.disconnected = DRPC_HandleDisconnect;
handlers.errored = DRPC_HandleError;
handlers.joinGame = DRPC_HandleJoin;
handlers.joinRequest = DRPC_HandleJoinRequest;
Discord_Initialize(DISCORD_APPID, &handlers, 1, NULL);
I_AddExitFunc(Discord_Shutdown);
DRPC_UpdatePresence();
}
/*--------------------------------------------------
void DRPC_SendDiscordInfo(void)
See header file for description.
--------------------------------------------------*/
void DRPC_SendDiscordInfo(void)
{
UINT8 buf[3];
UINT8 *p = buf;
UINT8 maxplayer;
if (!server)
return;
maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value));
WRITEUINT8(p, maxplayer);
WRITEUINT8(p, cv_allownewplayer.value);
WRITEUINT8(p, cv_discordinvites.value);
SendNetXCmd(XD_DISCORD, &buf, 3);
}
#ifdef HAVE_CURL
/*--------------------------------------------------
static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata)
Writing function for use with curl. Only intended to be used with simple text.
Input Arguments:-
s - Data to write
size - Always 1.
n - Length of data
userdata - Passed in from CURLOPT_WRITEDATA, intended to be SelfIPbuffer
Return:-
Number of bytes wrote in this pass.
--------------------------------------------------*/
static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata)
{
struct SelfIPbuffer *buffer;
size_t newlength;
buffer = userdata;
newlength = buffer->length + size*n;
buffer->pointer = realloc(buffer->pointer, newlength+1);
memcpy(buffer->pointer + buffer->length, s, size*n);
buffer->pointer[newlength] = '\0';
buffer->length = newlength;
return size*n;
}
#endif // HAVE_CURL
/*--------------------------------------------------
static const char *DRPC_GetServerIP(void)
Retrieves the IP address of the server that you're
connected to. Will attempt to use curl for getting your
own IP address, if it's not yours.
--------------------------------------------------*/
static const char *DRPC_GetServerIP(void)
{
const char *address;
// If you're connected
if (I_GetNodeAddress && (address = I_GetNodeAddress(servernode)) != NULL)
{
if (strcmp(address, "self"))
{
// We're not the server, so we could successfully get the IP!
// No need to do anything else :)
return address;
}
}
#ifdef HAVE_CURL
// This is a little bit goofy, but
// there's practically no good way to get your own public IP address,
// so we've gotta break out curl for this :V
if (!self_ip[0])
{
CURL *curl;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (curl)
{
// The API to get your public IP address from.
// Picked because it's stupid simple and it's been up for a long time.
const char *api = "http://ip4only.me/api/";
struct SelfIPbuffer buffer;
CURLcode success;
buffer.length = 0;
buffer.pointer = malloc(buffer.length+1);
buffer.pointer[0] = '\0';
curl_easy_setopt(curl, CURLOPT_URL, api);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DRPC_WriteServerIP);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
success = curl_easy_perform(curl);
if (success == CURLE_OK)
{
char *tmp;
tmp = strtok(buffer.pointer, ",");
if (!strcmp(tmp, "IPv4")) // ensure correct type of IP
{
tmp = strtok(NULL, ",");
strncpy(self_ip, tmp, IP_SIZE); // Yay, we have the IP :)
}
}
free(buffer.pointer);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
}
if (self_ip[0])
return self_ip;
else
#endif // HAVE_CURL
return NULL; // Could not get your IP for whatever reason, so we cannot do Discord invites
}
/*--------------------------------------------------
void DRPC_EmptyRequests(void)
Empties the request list. Any existing requests
will get an ignore reply.
--------------------------------------------------*/
static void DRPC_EmptyRequests(void)
{
while (discordRequestList != NULL)
{
Discord_Respond(discordRequestList->userID, DISCORD_REPLY_IGNORE);
DRPC_RemoveRequest(discordRequestList);
}
}
/*--------------------------------------------------
void DRPC_UpdatePresence(void)
See header file for description.
--------------------------------------------------*/
void DRPC_UpdatePresence(void)
{
char detailstr[48+1];
char mapimg[8+1];
char mapname[5+21+21+2+1];
char charimg[4+SKINNAMESIZE+1];
char charname[11+SKINNAMESIZE+1];
boolean joinSecretSet = false;
DiscordRichPresence discordPresence;
memset(&discordPresence, 0, sizeof(discordPresence));
if (!cv_discordrp.value)
{
// User doesn't want to show their game information, so update with empty presence.
// This just shows that they're playing SRB2Kart. (If that's too much, then they should disable game activity :V)
DRPC_EmptyRequests();
Discord_UpdatePresence(&discordPresence);
return;
}
#ifdef DEVELOP
// This way, we can use the invite feature in-dev, but not have snoopers seeing any potential secrets! :P
discordPresence.largeImageKey = "miscdevelop";
discordPresence.largeImageText = "No peeking!";
discordPresence.state = "Testing the game";
DRPC_EmptyRequests();
Discord_UpdatePresence(&discordPresence);
return;
#endif // DEVELOP
// Server info
if (netgame)
{
switch (ms_RoomId)
{
case -1: discordPresence.state = "Private"; break; // Private server
case 33: discordPresence.state = "Standard"; break;
case 28: discordPresence.state = "Casual"; break;
case 38: discordPresence.state = "Custom Gametypes"; break;
case 31: discordPresence.state = "OLDC"; break;
default: discordPresence.state = "Unknown Room"; break; // HOW
}
discordPresence.partyId = server_context; // Thanks, whoever gave us Mumble support, for implementing the EXACT thing Discord wanted for this field!
discordPresence.partySize = D_NumPlayers(); // Players in server
discordPresence.partyMax = discordInfo.maxPlayers; // Max players
if (DRPC_InvitesAreAllowed() == true)
{
const char *join;
// Grab the host's IP for joining.
if ((join = DRPC_GetServerIP()) != NULL)
{
char *xorjoin = DRPC_XORIPString(join);
discordPresence.joinSecret = xorjoin;
free(xorjoin);
joinSecretSet = true;
}
}
}
else
{
// Reset discord info if you're not in a place that uses it!
// Important for if you join a server that compiled without HAVE_DISCORDRPC,
// so that you don't ever end up using bad information from another server.
memset(&discordInfo, 0, sizeof(discordInfo));
// Offline info
if (Playing())
discordPresence.state = "Offline";
else if (demo.playback && !demo.title)
discordPresence.state = "Watching Replay";
else
discordPresence.state = "Menu";
}
// Gametype info
if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING) && Playing())
{
if (modeattacking)
discordPresence.details = "Time Attack";
else
{
snprintf(detailstr, 48, "%s%s%s",
gametype_cons_t[gametype].strvalue,
(gametype == GT_RACE) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "",
(encoremode == true) ? " | Encore" : ""
);
discordPresence.details = detailstr;
}
}
if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) // Map info
&& !(demo.playback && demo.title))
{
if ((gamemap >= 1 && gamemap <= 60) // supported race maps
|| (gamemap >= 136 && gamemap <= 164)) // supported battle maps
{
snprintf(mapimg, 8, "%s", G_BuildMapName(gamemap));
strlwr(mapimg);
discordPresence.largeImageKey = mapimg; // Map image
}
else if (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU)
{
// Hell map, use the method that got you here :P
discordPresence.largeImageKey = "miscdice";
}
else
{
// This is probably a custom map!
discordPresence.largeImageKey = "mapcustom";
}
if (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU)
{
// Hell map, hide the name
discordPresence.largeImageText = "Map: ???";
}
else
{
// Map name on tool tip
snprintf(mapname, 48, "Map: %s", G_BuildMapTitle(gamemap));
discordPresence.largeImageText = mapname;
}
if (gamestate == GS_LEVEL && Playing())
{
const time_t currentTime = time(NULL);
const time_t mapTimeStart = currentTime - ((leveltime + (modeattacking ? starttime : 0)) / TICRATE);
discordPresence.startTimestamp = mapTimeStart;
if (timelimitintics > 0)
{
const time_t mapTimeEnd = mapTimeStart + ((timelimitintics + starttime + 1) / TICRATE);
discordPresence.endTimestamp = mapTimeEnd;
}
}
}
else if (gamestate == GS_VOTING)
{
discordPresence.largeImageKey = (G_BattleGametype() ? "miscredplanet" : "miscblueplanet");
discordPresence.largeImageText = "Voting";
}
else
{
discordPresence.largeImageKey = "misctitle";
discordPresence.largeImageText = "Title Screen";
}
// Character info
if (Playing() && playeringame[consoleplayer] && !players[consoleplayer].spectator)
{
// Supported skin names
static const char *supportedSkins[] = {
// base game
"sonic",
"tails",
"knuckles",
"eggman",
"metalsonic",
// bonus chars
"flicky",
"motobug",
"amy",
"mighty",
"ray",
"espio",
"vector",
"chao",
"gamma",
"chaos",
"shadow",
"rouge",
"herochao",
"darkchao",
"cream",
"omega",
"blaze",
"silver",
"wonderboy",
"arle",
"nights",
"sakura",
"ulala",
"beat",
"vyse",
"aiai",
"kiryu",
"aigis",
"miku",
"doom",
NULL
};
boolean customChar = true;
UINT8 checkSkin = 0;
// Character image
while (supportedSkins[checkSkin] != NULL)
{
if (!strcmp(skins[players[consoleplayer].skin].name, supportedSkins[checkSkin]))
{
snprintf(charimg, 21, "char%s", supportedSkins[checkSkin]);
discordPresence.smallImageKey = charimg;
customChar = false;
break;
}
checkSkin++;
}
if (customChar == true)
{
// Use the custom character icon!
discordPresence.smallImageKey = "charcustom";
}
snprintf(charname, 28, "Character: %s", skins[players[consoleplayer].skin].realname);
discordPresence.smallImageText = charname; // Character name
}
if (joinSecretSet == false)
{
// Not able to join? Flush the request list, if it exists.
DRPC_EmptyRequests();
}
Discord_UpdatePresence(&discordPresence);
}
#endif // HAVE_DISCORDRPC

91
src/discord.h Normal file
View file

@ -0,0 +1,91 @@
// SONIC ROBO BLAST 2 KART
//-----------------------------------------------------------------------------
// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour.
// Copyright (C) 2018-2020 by Kart Krew.
//
// 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
#ifndef __DISCORD__
#define __DISCORD__
#ifdef HAVE_DISCORDRPC
#include "discord_rpc.h"
extern consvar_t cv_discordrp;
extern consvar_t cv_discordstreamer;
extern consvar_t cv_discordasks;
extern consvar_t cv_discordinvites;
extern struct discordInfo_s {
UINT8 maxPlayers;
boolean joinsAllowed;
boolean everyoneCanInvite;
} discordInfo;
typedef struct discordRequest_s {
char *username; // Discord user name.
char *discriminator; // Discord discriminator (The little hashtag thing after the username). Separated for a "hide discriminators" cvar.
char *userID; // The ID of the Discord user, gets used with Discord_Respond()
// HAHAHA, no.
// *Maybe* if it was only PNG I would boot up curl just to get AND convert this to Doom GFX,
// but it can *also* be a JEPG, WebP, or GIF :)
// Hey, wanna add ImageMagick as a dependency? :dying:
//patch_t *avatar;
struct discordRequest_s *next; // Next request in the list.
struct discordRequest_s *prev; // Previous request in the list. Not used normally, but just in case something funky happens, this should repair the list.
} discordRequest_t;
extern discordRequest_t *discordRequestList;
/*--------------------------------------------------
void DRPC_RemoveRequest(void);
Removes an invite from the list.
--------------------------------------------------*/
void DRPC_RemoveRequest(discordRequest_t *removeRequest);
/*--------------------------------------------------
void DRPC_Init(void);
Initalizes Discord Rich Presence by linking the Application ID
and setting the callback functions.
--------------------------------------------------*/
void DRPC_Init(void);
/*--------------------------------------------------
void DRPC_SendDiscordInfo(void);
Sends the server's information needed for
the rich presence state.
--------------------------------------------------*/
void DRPC_SendDiscordInfo(void);
/*--------------------------------------------------
void DRPC_UpdatePresence(void);
Updates what is displayed by Rich Presence on the user's profile.
Should be called whenever something that is displayed is
changed in-game.
--------------------------------------------------*/
void DRPC_UpdatePresence(void);
#endif // HAVE_DISCORDRPC
#endif // __DISCORD__

View file

@ -51,6 +51,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;
@ -4717,7 +4721,7 @@ char *G_BuildMapTitle(INT32 mapnum)
}
else if (!(mapheaderinfo[mapnum-1]->levelflags & LF_NOZONE))
{
zonetext = M_GetText("ZONE");
zonetext = M_GetText("Zone");
len += strlen(zonetext) + 1; // ' ' + zonetext
}
if (strlen(mapheaderinfo[mapnum-1]->actnum) > 0)
@ -6381,7 +6385,7 @@ void G_BeginRecording(void)
// Full replay title
demo_p += 64;
snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Record Attack" : connectedservername);
snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Time Attack" : connectedservername);
// demo checksum
demo_p += 16;
@ -8405,6 +8409,9 @@ boolean G_DemoTitleResponder(event_t *ev)
void G_SetGamestate(gamestate_t newstate)
{
gamestate = newstate;
#ifdef HAVE_DISCORDRPC
DRPC_UpdatePresence();
#endif
}
/* These functions handle the exitgame flag. Before, when the user

View file

@ -318,6 +318,15 @@ const CPUInfoFlags *I_CPUInfo(void);
*/
const char *I_LocateWad(void);
/** \brief Save current wad directory to appdata
*/
void I_SaveCurrentWadDirectory(void);
/** \brief Change directory to last known directory saved in appdata
\return whether the directory could be saved
*/
boolean I_UseSavedWadDirectory(void);
/** \brief First Joystick's events
*/
void I_GetJoystickEvents(void);

View file

@ -81,6 +81,11 @@ int snprintf(char *str, size_t n, const char *fmt, ...);
//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
#endif
#ifdef HAVE_DISCORDRPC
//#include "discord_rpc.h"
#include "discord.h"
#endif
#define SKULLXOFF -32
#define LINEHEIGHT 16
#define STRINGHEIGHT 8
@ -188,6 +193,12 @@ static void M_RoomMenu(INT32 choice);
// the haxor message menu
menu_t MessageDef;
#ifdef HAVE_DISCORDRPC
menu_t MISC_DiscordRequestsDef;
static void M_HandleDiscordRequests(INT32 choice);
static void M_DrawDiscordRequests(void);
#endif
menu_t SPauseDef;
#define lsheadingheight 16
@ -293,6 +304,9 @@ menu_t OP_SoundOptionsDef;
//Misc
menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
#ifdef HAVE_DISCORDRPC
menu_t OP_DiscordOptionsDef;
#endif
menu_t OP_HUDOptionsDef, OP_ChatOptionsDef;
menu_t OP_GameOptionsDef, OP_ServerOptionsDef;
#ifndef NONET
@ -585,6 +599,10 @@ static menuitem_t MPauseMenu[] =
{IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16},
{IT_STRING | IT_CALL, NULL, "Switch Map..." , M_MapChange, 24},
#ifdef HAVE_DISCORDRPC
{IT_STRING | IT_SUBMENU, NULL, "Ask To Join Requests...", &MISC_DiscordRequestsDef, 24},
#endif
{IT_CALL | IT_STRING, NULL, "Continue", M_SelectableClearMenus, 40},
{IT_CALL | IT_STRING, NULL, "P1 Setup...", M_SetupMultiPlayer, 48}, // splitscreen
{IT_CALL | IT_STRING, NULL, "P2 Setup...", M_SetupMultiPlayer2, 56}, // splitscreen
@ -608,6 +626,9 @@ typedef enum
mpause_addons = 0,
mpause_scramble,
mpause_switchmap,
#ifdef HAVE_DISCORDRPC
mpause_discordrequests,
#endif
mpause_continue,
mpause_psetupsplit,
@ -658,6 +679,13 @@ typedef enum
spause_quit
} spause_e;
#ifdef HAVE_DISCORDRPC
static menuitem_t MISC_DiscordRequestsMenu[] =
{
{IT_KEYHANDLER|IT_NOTHING, NULL, "", M_HandleDiscordRequests, 0},
};
#endif
// -----------------
// Misc menu options
// -----------------
@ -1343,11 +1371,17 @@ static menuitem_t OP_SoundOptionsMenu[] =
static menuitem_t OP_DataOptionsMenu[] =
{
{IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 10},
{IT_STRING | IT_CALL, NULL, "Addon Options...", M_AddonsOptions, 20},
{IT_STRING | IT_SUBMENU, NULL, "Replay Options...", &MISC_ReplayOptionsDef, 30},
#ifdef HAVE_DISCORDRPC
{IT_STRING | IT_SUBMENU, NULL, "Discord Options...", &OP_DiscordOptionsDef, 40},
{IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 60},
#else
{IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 50},
#endif
};
static menuitem_t OP_ScreenshotOptionsMenu[] =
@ -1396,7 +1430,7 @@ static menuitem_t OP_AddonsOptionsMenu[] =
{IT_HEADER, NULL, "Menu", NULL, 0},
{IT_STRING|IT_CVAR, NULL, "Location", &cv_addons_option, 10},
{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_addons_folder, 20},
{IT_STRING|IT_CVAR, NULL, "Identify addons via", &cv_addons_md5, 48},
{IT_STRING|IT_CVAR, NULL, "Identify addons via", &cv_addons_md5, 48},
{IT_STRING|IT_CVAR, NULL, "Show unsupported file types", &cv_addons_showall, 58},
{IT_HEADER, NULL, "Search", NULL, 76},
@ -1409,6 +1443,19 @@ enum
op_addons_folder = 2,
};
#ifdef HAVE_DISCORDRPC
static menuitem_t OP_DiscordOptionsMenu[] =
{
{IT_STRING | IT_CVAR, NULL, "Rich Presence", &cv_discordrp, 10},
{IT_HEADER, NULL, "Rich Presence Settings", NULL, 30},
{IT_STRING | IT_CVAR, NULL, "Streamer Mode", &cv_discordstreamer, 40},
{IT_STRING | IT_CVAR, NULL, "Allow Ask To Join", &cv_discordasks, 60},
{IT_STRING | IT_CVAR, NULL, "Allow Invites", &cv_discordinvites, 70},
};
#endif
static menuitem_t OP_HUDOptionsMenu[] =
{
{IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", &cv_showhud, 10},
@ -1649,6 +1696,19 @@ menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72);
menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72);
menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72);
#ifdef HAVE_DISCORDRPC
menu_t MISC_DiscordRequestsDef = {
NULL,
sizeof (MISC_DiscordRequestsMenu)/sizeof (menuitem_t),
&MPauseDef,
MISC_DiscordRequestsMenu,
M_DrawDiscordRequests,
0, 0,
0,
NULL
};
#endif
// Misc Main Menu
menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40);
menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40);
@ -2075,6 +2135,9 @@ menu_t OP_OpenGLColorDef =
menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30);
menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
#ifdef HAVE_DISCORDRPC
menu_t OP_DiscordOptionsDef = DEFAULTMENUSTYLE(NULL, OP_DiscordOptionsMenu, &OP_DataOptionsDef, 30, 30);
#endif
menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 30, 30);
// ==========================================================================
@ -3194,12 +3257,18 @@ void M_StartControlPanel(void)
MPauseMenu[mpause_psetup].status = IT_DISABLED;
MISC_ChangeTeamMenu[0].status = IT_DISABLED;
MISC_ChangeSpectateMenu[0].status = IT_DISABLED;
// Reset these in case splitscreen messes things up
MPauseMenu[mpause_addons].alphaKey = 8;
MPauseMenu[mpause_scramble].alphaKey = 8;
MPauseMenu[mpause_switchmap].alphaKey = 24;
MPauseMenu[mpause_switchteam].alphaKey = 48;
MPauseMenu[mpause_switchspectate].alphaKey = 48;
MPauseMenu[mpause_options].alphaKey = 64;
MPauseMenu[mpause_title].alphaKey = 80;
MPauseMenu[mpause_quit].alphaKey = 88;
Dummymenuplayer_OnChange();
if ((server || IsPlayerAdmin(consoleplayer)))
@ -3271,6 +3340,19 @@ void M_StartControlPanel(void)
MPauseMenu[mpause_spectate].status = IT_GRAYEDOUT;
}
#ifdef HAVE_DISCORDRPC
{
UINT8 i;
for (i = 0; i < mpause_discordrequests; i++)
MPauseMenu[i].alphaKey -= 8;
MPauseMenu[mpause_discordrequests].alphaKey = MPauseMenu[i].alphaKey;
M_RefreshPauseMenu();
}
#endif
currentMenu = &MPauseDef;
itemOn = mpause_continue;
}
@ -4098,6 +4180,25 @@ static void M_DrawPauseMenu(void)
}
#endif
#ifdef HAVE_DISCORDRPC
// kind of hackily baked in here
if (currentMenu == &MPauseDef && discordRequestList != NULL)
{
const tic_t freq = TICRATE/2;
if ((leveltime % freq) >= freq/2)
{
V_DrawFixedPatch(204 * FRACUNIT,
(currentMenu->y + MPauseMenu[mpause_discordrequests].alphaKey - 1) * FRACUNIT,
FRACUNIT,
0,
W_CachePatchName("K_REQUE2", PU_CACHE),
NULL
);
}
}
#endif
M_DrawGenericMenu();
}
@ -6221,7 +6322,12 @@ static void M_Options(INT32 choice)
OP_MainMenu[4].status = OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
OP_MainMenu[8].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); // Play credits
#ifdef HAVE_DISCORDRPC
OP_DataOptionsMenu[4].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data
#else
OP_DataOptionsMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data
#endif
OP_GameOptionsMenu[3].status =
(M_SecretUnlocked(SECRET_ENCORE)) ? (IT_CVAR|IT_STRING) : IT_SECRET; // cv_kartencore
@ -6262,6 +6368,20 @@ static void M_SelectableClearMenus(INT32 choice)
M_ClearMenus(true);
}
void M_RefreshPauseMenu(void)
{
#ifdef HAVE_DISCORDRPC
if (discordRequestList != NULL)
{
MPauseMenu[mpause_discordrequests].status = IT_STRING | IT_SUBMENU;
}
else
{
MPauseMenu[mpause_discordrequests].status = IT_GRAYEDOUT;
}
#endif
}
// ======
// CHEATS
// ======
@ -7460,7 +7580,7 @@ static void M_DrawStatsMaps(int location)
else
V_DrawString(20, y, 0, va("%s %s %s",
mapheaderinfo[mnum]->lvlttl,
(mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "ZONE"),
(mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "Zone"),
mapheaderinfo[mnum]->actnum));
y += 8;
@ -11263,3 +11383,161 @@ static void M_OGL_DrawColorMenu(void)
highlightflags, "Gamma correction");
}
#endif
#ifdef HAVE_DISCORDRPC
static const tic_t confirmLength = 3*TICRATE/4;
static tic_t confirmDelay = 0;
static boolean confirmAccept = false;
static void M_HandleDiscordRequests(INT32 choice)
{
if (confirmDelay > 0)
return;
switch (choice)
{
case KEY_ENTER:
Discord_Respond(discordRequestList->userID, DISCORD_REPLY_YES);
confirmAccept = true;
confirmDelay = confirmLength;
S_StartSound(NULL, sfx_s3k63);
break;
case KEY_ESCAPE:
Discord_Respond(discordRequestList->userID, DISCORD_REPLY_NO);
confirmAccept = false;
confirmDelay = confirmLength;
S_StartSound(NULL, sfx_s3kb2);
break;
}
}
static const char *M_GetDiscordName(discordRequest_t *r)
{
if (r == NULL)
return "";
if (cv_discordstreamer.value)
return r->username;
return va("%s#%s", r->username, r->discriminator);
}
// (this goes in k_hud.c when merged into v2)
static void M_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean small)
{
patch_t *stickerEnd;
INT32 height;
if (small == true)
{
stickerEnd = W_CachePatchName("K_STIKE2", PU_CACHE);
height = 6;
}
else
{
stickerEnd = W_CachePatchName("K_STIKEN", PU_CACHE);
height = 11;
}
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, flags, stickerEnd, NULL);
V_DrawFill(x, y, width, height, 24|flags);
V_DrawFixedPatch((x + width)*FRACUNIT, y*FRACUNIT, FRACUNIT, flags|V_FLIP, stickerEnd, NULL);
}
static void M_DrawDiscordRequests(void)
{
discordRequest_t *curRequest = discordRequestList;
UINT8 *colormap;
patch_t *hand = NULL;
boolean removeRequest = false;
const char *wantText = "...would like to join!";
const char *controlText = "\x82" "ENTER" "\x80" " - Accept " "\x82" "ESC" "\x80" " - Decline";
INT32 x = 100;
INT32 y = 133;
INT32 slide = 0;
INT32 maxYSlide = 18;
if (confirmDelay > 0)
{
if (confirmAccept == true)
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_MENUCACHE);
hand = W_CachePatchName("K_LAPH02", PU_CACHE);
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE);
hand = W_CachePatchName("K_LAPH03", PU_CACHE);
}
slide = confirmLength - confirmDelay;
confirmDelay--;
if (confirmDelay == 0)
removeRequest = true;
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREY, GTC_MENUCACHE);
}
V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_LAPE01", PU_CACHE), colormap);
if (hand != NULL)
{
fixed_t handoffset = (4 - abs((signed)(skullAnimCounter - 4))) * FRACUNIT;
V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT + handoffset, FRACUNIT, 0, hand, NULL);
}
M_DrawSticker(x + (slide * 32), y - 1, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false);
V_DrawThinString(x + (slide * 32), y, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_YELLOWMAP, M_GetDiscordName(curRequest));
M_DrawSticker(x, y + 12, V_ThinStringWidth(wantText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true);
V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE|V_6WIDTHSPACE, wantText);
M_DrawSticker(x, y + 26, V_ThinStringWidth(controlText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true);
V_DrawThinString(x, y + 24, V_ALLOWLOWERCASE|V_6WIDTHSPACE, controlText);
y -= 18;
while (curRequest->next != NULL)
{
INT32 ySlide = min(slide * 4, maxYSlide);
curRequest = curRequest->next;
M_DrawSticker(x, y - 1 + ySlide, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false);
V_DrawThinString(x, y + ySlide, V_ALLOWLOWERCASE|V_6WIDTHSPACE, M_GetDiscordName(curRequest));
y -= 12;
maxYSlide = 12;
}
if (removeRequest == true)
{
DRPC_RemoveRequest(discordRequestList);
if (discordRequestList == NULL)
{
// No other requests
MPauseMenu[mpause_discordrequests].status = IT_GRAYEDOUT;
if (currentMenu->prevMenu)
{
M_SetupNextMenu(currentMenu->prevMenu);
if (currentMenu == &MPauseDef)
itemOn = mpause_continue;
}
else
M_ClearMenus(true);
return;
}
}
}
#endif

View file

@ -267,6 +267,8 @@ void Addons_option_Onchange(void);
void M_ReplayHut(INT32 choice);
void M_SetPlaybackMenuPointer(void);
void M_RefreshPauseMenu(void);
INT32 HU_GetHighlightColor(void);
// These defines make it a little easier to make menus

View file

@ -23,6 +23,10 @@
#include "m_menu.h"
#include "z_zone.h"
#ifdef HAVE_DISCORDRPC
#include "discord.h"
#endif
#ifdef MASTERSERVER
static int MSId;
@ -266,6 +270,10 @@ Finish_update (void)
if (! done)
Finish_update();
#ifdef HAVE_DISCORDRPC
else
DRPC_UpdatePresence();
#endif
}
static void
@ -303,6 +311,10 @@ Finish_unlist (void)
MSId++;
}
Unlock_state();
#ifdef HAVE_DISCORDRPC
DRPC_UpdatePresence();
#endif
}
#ifdef HAVE_THREADS

View file

@ -2962,7 +2962,7 @@ boolean P_SetupLevel(boolean skipprecip)
snprintf(tx, 63, "%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"),
((mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " Zone"),
(strlen(mapheaderinfo[gamemap-1]->actnum) > 0) ? va(", Act %s",mapheaderinfo[gamemap-1]->actnum) : "");
V_DrawSmallString(1, 195, V_ALLOWLOWERCASE, tx);
I_UpdateNoVsync();

View file

@ -70,6 +70,8 @@ if(${SDL2_FOUND})
set(SRB2_SDL2_TOTAL_SOURCES
${SRB2_CORE_SOURCES}
${SRB2_CORE_HEADERS}
${SRB2_DISCORDRPC_SOURCES}
${SRB2_DISCORDRPC_HEADERS}
${SRB2_PNG_SOURCES}
${SRB2_PNG_HEADERS}
${SRB2_CORE_RENDER_SOURCES}
@ -86,9 +88,11 @@ if(${SDL2_FOUND})
${SRB2_PNG_SOURCES} ${SRB2_PNG_HEADERS})
source_group("Renderer" FILES ${SRB2_CORE_RENDER_SOURCES})
source_group("Game" FILES ${SRB2_CORE_GAME_SOURCES})
source_group("Discord Rich Presence" FILES ${SRB2_DISCORDRPC_SOURCES} ${SRB2_DISCORDRPC_HEADERS})
source_group("Assembly" FILES ${SRB2_ASM_SOURCES} ${SRB2_NASM_SOURCES})
source_group("LUA" FILES ${SRB2_LUA_SOURCES} ${SRB2_LUA_HEADERS})
source_group("LUA\\Interpreter" FILES ${SRB2_BLUA_SOURCES} ${SRB2_BLUA_HEADERS})
if(${SRB2_CONFIG_HWRENDER})
set(SRB2_SDL2_TOTAL_SOURCES ${SRB2_SDL2_TOTAL_SOURCES}
@ -153,6 +157,7 @@ if(${SDL2_FOUND})
${ZLIB_LIBRARIES}
${OPENGL_LIBRARIES}
${CURL_LIBRARIES}
${DISCORDRPC_LIBRARIES}
)
set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
else()
@ -164,6 +169,7 @@ if(${SDL2_FOUND})
${ZLIB_LIBRARIES}
${OPENGL_LIBRARIES}
${CURL_LIBRARIES}
${DISCORDRPC_LIBRARIES}
)
if(${CMAKE_SYSTEM} MATCHES Linux)
@ -244,6 +250,7 @@ if(${SDL2_FOUND})
${ZLIB_INCLUDE_DIRS}
${OPENGL_INCLUDE_DIRS}
${CURL_INCLUDE_DIRS}
${DISCORDRPC_INCLUDE_DIRS}
)
if(${SRB2_HAVE_MIXER})
@ -328,6 +335,10 @@ if(${SDL2_FOUND})
getwinlib(libgme "libgme.dll")
endif()
if(${SRB2_CONFIG_HAVE_DISCORDRPC})
getwinlib(discord-rpc "discord-rpc.dll")
endif()
install(PROGRAMS
${win_extra_dll_list}
DESTINATION .

View file

@ -34,6 +34,7 @@
#ifdef _WIN32
#define RPC_NO_WINDOWS_H
#include <windows.h>
#include <shlobj.h>
#include "../doomtype.h"
typedef BOOL (WINAPI *p_GetDiskFreeSpaceExA)(LPCSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
typedef BOOL (WINAPI *p_IsProcessorFeaturePresent) (DWORD);
@ -3770,6 +3771,65 @@ static const char *locateWad(void)
return NULL;
}
#ifdef _WIN32
static FILE * openAppDataFile(const char *filename, const char *mode)
{
FILE * file = NULL;
char kdir[MAX_PATH];
if (SHGetFolderPathAndSubDirA(NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
NULL, 0, "SRB2Kart", kdir) == S_OK)
{
strcat(kdir, "\\");
strcat(kdir, filename);
file = fopen(kdir, mode);
}
return file;
}
#endif
void I_SaveCurrentWadDirectory(void)
{
#ifdef _WIN32
char path[MAX_PATH];
FILE * file = openAppDataFile("lastwaddir", "w");
if (file != NULL)
{
if (strcmp(srb2path, ".") == 0)
{
GetCurrentDirectoryA(sizeof path, path);
fputs(path, file);
}
else
{
fputs(srb2path, file);
}
fclose(file);
}
#endif
}
boolean I_UseSavedWadDirectory(void)
{
boolean ok = false;
#ifdef _WIN32
FILE * file = openAppDataFile("lastwaddir", "r");
if (file != NULL)
{
if (fgets(srb2path, sizeof srb2path, file) != NULL)
{
I_OutputMsg(
"Going to the last known directory with srb2.srb: %s\n",
srb2path);
ok = SetCurrentDirectoryA(srb2path);
}
fclose(file);
}
#endif
return ok;
}
const char *I_LocateWad(void)
{
const char *waddir;

View file

@ -81,6 +81,10 @@
#include "ogl_sdl.h"
#endif
#ifdef HAVE_DISCORDRPC
#include "../discord.h"
#endif
// maximum number of windowed modes (see windowedModes[][])
#define MAXWINMODES (18)
@ -1387,6 +1391,11 @@ void I_FinishUpdate(void)
if (cv_showping.value && netgame && consoleplayer != serverplayer)
SCR_DisplayLocalPing();
#ifdef HAVE_DISCORDRPC
if (discordRequestList != NULL)
ST_AskToJoinEnvelope();
#endif
if (rendermode == render_soft && screens[0])
{
SDL_Rect rect;

View file

@ -816,6 +816,10 @@ sfxinfo_t S_sfx[NUMSFX] =
{"mkuma", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Trigger Happy Havoc Monokuma
{"toada", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // Arid Sands Toad scream
{"bsnipe", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Banana sniping
{"join", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Player joined server
{"leave", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Player left server
{"requst", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Got a Discord join request
{"syfail", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Funny sync failure
{"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // :shitsfree:
{"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Debug notification

View file

@ -891,6 +891,10 @@ typedef enum
sfx_mkuma,
sfx_toada,
sfx_bsnipe,
sfx_join,
sfx_leave,
sfx_requst,
sfx_syfail,
sfx_itfree,
sfx_dbgsal,

View file

@ -129,6 +129,11 @@ static patch_t *gotbflag;
static patch_t *hud_tv1;
static patch_t *hud_tv2;
#ifdef HAVE_DISCORDRPC
// Discord Rich Presence
static patch_t *envelope;
#endif
// SRB2kart
hudinfo_t hudinfo[NUMHUDITEMS] =
@ -349,6 +354,11 @@ void ST_LoadGraphics(void)
// Midnight Channel:
hud_tv1 = W_CachePatchName("HUD_TV1", PU_HUDGFX);
hud_tv2 = W_CachePatchName("HUD_TV2", PU_HUDGFX);
#ifdef HAVE_DISCORDRPC
// Discord Rich Presence
envelope = W_CachePatchName("K_REQUES", PU_HUDGFX);
#endif
}
// made separate so that skins code can reload custom face graphics
@ -776,7 +786,7 @@ static void ST_drawLevelTitle(void)
if (zonttl[0])
zonexpos -= V_LevelNameWidth(zonttl); // SRB2kart
else
zonexpos -= V_LevelNameWidth(M_GetText("ZONE"));
zonexpos -= V_LevelNameWidth(M_GetText("Zone"));
}
if (lvlttlxpos < 0)
@ -813,7 +823,7 @@ static void ST_drawLevelTitle(void)
if (strlen(zonttl) > 0)
V_DrawLevelTitle(zonexpos, bary+6, 0, zonttl);
else if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
V_DrawLevelTitle(zonexpos, bary+6, 0, M_GetText("ZONE"));
V_DrawLevelTitle(zonexpos, bary+6, 0, M_GetText("Zone"));
if (actnum[0])
V_DrawLevelTitle(ttlnumxpos+12, bary+6, 0, actnum);
@ -2080,6 +2090,22 @@ static void ST_MayonakaStatic(void)
V_DrawFixedPatch(320<<FRACBITS, 142<<FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_FLIP|flag, hud_tv2, NULL);
}
#ifdef HAVE_DISCORDRPC
void ST_AskToJoinEnvelope(void)
{
const tic_t freq = TICRATE/2;
if (menuactive)
return;
if ((leveltime % freq) < freq/2)
return;
V_DrawFixedPatch(296*FRACUNIT, 2*FRACUNIT, FRACUNIT, V_SNAPTOTOP|V_SNAPTORIGHT, envelope, NULL);
// maybe draw number of requests with V_DrawPingNum ?
}
#endif
void ST_Drawer(void)
{
UINT8 i;

View file

@ -29,6 +29,11 @@ void ST_Ticker(void);
// Called when naming a replay.
void ST_DrawDemoTitleEntry(void);
#ifdef HAVE_DISCORDRPC
// Called when you have Discord asks
void ST_AskToJoinEnvelope(void);
#endif
// Called by main loop.
void ST_Drawer(void);

View file

@ -30,6 +30,8 @@ ifndef MINGW64 #miniupnc is broken with MINGW64
endif
endif
HAVE_DISCORDRPC=1
OPTS=-DSTDC_HEADERS
ifndef GCC44
@ -142,4 +144,15 @@ ifdef MINGW64
else
CURL_LDFLAGS+=-L../libs/curl/lib32 -lcurl
endif #MINGW64
endif
endif
ifdef HAVE_DISCORDRPC
ifdef MINGW64
CPPFLAGS+=-I../libs/discord-rpc/win64-dynamic/include
LDFLAGS+=-L../libs/discord-rpc/win64-dynamic/lib
else
CPPFLAGS+=-I../libs/discord-rpc/win32-dynamic/include
LDFLAGS+=-L../libs/discord-rpc/win32-dynamic/lib
endif
LIBS+=-ldiscord-rpc
endif

View file

@ -233,7 +233,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32))
}
else
{
const char *zonttl = (mapheaderinfo[prevmap]->zonttl[0] ? mapheaderinfo[prevmap]->zonttl : "ZONE");
const char *zonttl = (mapheaderinfo[prevmap]->zonttl[0] ? mapheaderinfo[prevmap]->zonttl : "Zone");
if (mapheaderinfo[prevmap]->actnum[0])
snprintf(data.match.levelstring,
sizeof data.match.levelstring,

View file

@ -1,11 +0,0 @@
SRB2Kart Install Instructions
1. Move every file from the "new-install" folder to this main install folder.
2. DELETE "staging.bat" and "staging.txt"! These can mess up your installation if run by accident!
3. Optionally, create a folder in your user profile named "SRB2Kart". This is where your game data and addons may live. For example,
C:\Users\[User]\SRB2Kart
4. Run the game! Double-click srb2kart.exe -- or see if you have Start Menu icons under "SRB2Kart".