2020-08-17 01:31:03 +00:00
|
|
|
// SONIC ROBO BLAST 2 KART
|
2018-10-22 04:34:45 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
2020-08-17 01:31:03 +00:00
|
|
|
// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour.
|
|
|
|
// Copyright (C) 2018-2020 by Kart Krew.
|
2018-10-22 04:34:45 +00:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
|
2020-08-17 04:29:52 +00:00
|
|
|
#ifdef HAVE_CURL
|
|
|
|
#include <curl/curl.h>
|
|
|
|
#endif
|
|
|
|
|
2018-10-22 04:34:45 +00:00
|
|
|
#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 "doomdef.h"
|
|
|
|
|
2020-08-17 06:25:05 +00:00
|
|
|
// Feel free to provide your own, if you care enough to create another Discord app for this :P
|
|
|
|
#define DISCORD_APPID "503531144395096085"
|
|
|
|
|
|
|
|
consvar_t cv_discordrp = {"discordrp", "On", CV_SAVE|CV_CALL, CV_OnOff, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL};
|
2018-10-30 09:44:29 +00:00
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
#ifdef HAVE_CURL
|
|
|
|
struct SelfIPbuffer
|
|
|
|
{
|
|
|
|
CURL *curl;
|
|
|
|
char *pointer;
|
|
|
|
size_t length;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define IP_SIZE 16
|
|
|
|
static char self_ip[IP_SIZE];
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*--------------------------------------------------
|
|
|
|
static void DRPC_HandleReady(const DiscordUser *user)
|
|
|
|
|
|
|
|
Handler 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)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
|
|
|
CONS_Printf("Discord: connected to %s#%s - %s\n", user->username, user->discriminator, user->userId);
|
|
|
|
}
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
static void DRPC_HandleDisconnect(int err, const char *msg)
|
|
|
|
|
|
|
|
Handler 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)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
|
|
|
CONS_Printf("Discord: disconnected (%d: %s)\n", err, msg);
|
|
|
|
}
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
static void DRPC_HandleError(int err, const char *msg)
|
|
|
|
|
|
|
|
Handler 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)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
2020-08-17 10:13:32 +00:00
|
|
|
CONS_Alert(CONS_WARNING, "Discord error (%d: %s)\n", err, msg);
|
2018-10-22 04:34:45 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
static void DRPC_HandleJoin(const char *secret)
|
|
|
|
|
|
|
|
Handler 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)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
2020-08-17 10:13:32 +00:00
|
|
|
CONS_Printf("Connecting to %s via Discord\n", secret);
|
2018-10-22 04:34:45 +00:00
|
|
|
COM_BufAddText(va("connect \"%s\"\n", secret));
|
|
|
|
}
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
void DRPC_Init(void)
|
|
|
|
|
|
|
|
See header file for description.
|
|
|
|
--------------------------------------------------*/
|
2018-10-22 04:34:45 +00:00
|
|
|
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;
|
|
|
|
|
2018-10-30 09:44:29 +00:00
|
|
|
Discord_Initialize(DISCORD_APPID, &handlers, 1, NULL);
|
2018-10-22 04:34:45 +00:00
|
|
|
I_AddExitFunc(Discord_Shutdown);
|
|
|
|
DRPC_UpdatePresence();
|
|
|
|
}
|
|
|
|
|
2020-08-17 04:29:52 +00:00
|
|
|
#ifdef HAVE_CURL
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata)
|
2020-08-17 04:29:52 +00:00
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
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
|
2020-08-17 04:29:52 +00:00
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
Return:-
|
|
|
|
Number of bytes wrote in this pass.
|
|
|
|
--------------------------------------------------*/
|
|
|
|
static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata)
|
2020-08-17 04:29:52 +00:00
|
|
|
{
|
|
|
|
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
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
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.
|
|
|
|
--------------------------------------------------*/
|
2020-08-17 04:29:52 +00:00
|
|
|
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"))
|
2020-08-17 10:13:32 +00:00
|
|
|
{
|
|
|
|
// We're not the server, so we could successfully get the IP!
|
|
|
|
// No need to do anything else :)
|
|
|
|
return address;
|
|
|
|
}
|
2020-08-17 04:29:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#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)
|
|
|
|
{
|
2020-08-17 10:13:32 +00:00
|
|
|
// 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/";
|
|
|
|
|
2020-08-17 04:29:52 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self_ip[0])
|
|
|
|
return self_ip;
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
return NULL; // Could not get your IP for whatever reason, so we cannot do Discord invites
|
|
|
|
}
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
/*--------------------------------------------------
|
|
|
|
void DRPC_UpdatePresence(void)
|
|
|
|
|
|
|
|
See header file for description.
|
|
|
|
--------------------------------------------------*/
|
2018-10-22 04:34:45 +00:00
|
|
|
void DRPC_UpdatePresence(void)
|
|
|
|
{
|
2020-08-17 08:42:22 +00:00
|
|
|
char mapimg[8+1];
|
|
|
|
char mapname[5+21+21+2+1];
|
|
|
|
|
|
|
|
char charimg[4+SKINNAMESIZE+1];
|
|
|
|
char charname[11+SKINNAMESIZE+1];
|
2020-08-17 04:29:52 +00:00
|
|
|
|
2018-10-22 04:34:45 +00:00
|
|
|
DiscordRichPresence discordPresence;
|
|
|
|
memset(&discordPresence, 0, sizeof(discordPresence));
|
|
|
|
|
2020-08-17 06:25:05 +00:00
|
|
|
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)
|
|
|
|
Discord_UpdatePresence(&discordPresence);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-30 09:44:29 +00:00
|
|
|
// Server info
|
|
|
|
if (netgame)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
2020-08-17 04:29:52 +00:00
|
|
|
const char *join;
|
2018-10-30 09:44:29 +00:00
|
|
|
|
|
|
|
switch (ms_RoomId)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
2018-10-30 09:44:29 +00:00
|
|
|
case -1: discordPresence.state = "Private"; break; // Private server
|
|
|
|
case 33: discordPresence.state = "Standard"; break;
|
|
|
|
case 28: discordPresence.state = "Casual"; break;
|
2020-08-17 04:29:52 +00:00
|
|
|
case 38: discordPresence.state = "Custom Gametypes"; break;
|
2020-08-17 06:05:16 +00:00
|
|
|
case 31: discordPresence.state = "OLDC"; break;
|
2020-08-17 04:29:52 +00:00
|
|
|
default: discordPresence.state = "Unknown Room"; break; // HOW
|
2018-10-22 04:34:45 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 04:29:52 +00:00
|
|
|
discordPresence.partyId = server_context; // Thanks, whoever gave us Mumble support, for implementing the EXACT thing Discord wanted for this field!
|
2020-08-17 06:05:16 +00:00
|
|
|
discordPresence.partySize = D_NumPlayers(); // Players in server
|
2020-08-17 06:25:05 +00:00
|
|
|
discordPresence.partyMax = cv_maxplayers.value; // Max players (TODO: another variable should hold this, so that maxplayers doesn't have to be a netvar)
|
2018-10-22 04:34:45 +00:00
|
|
|
|
2020-08-17 04:29:52 +00:00
|
|
|
// Grab the host's IP for joining.
|
2020-08-17 10:55:43 +00:00
|
|
|
if (cv_allownewplayer.value && ((join = DRPC_GetServerIP()) != NULL))
|
2020-08-17 04:29:52 +00:00
|
|
|
discordPresence.joinSecret = join;
|
2018-10-30 09:44:29 +00:00
|
|
|
}
|
|
|
|
else
|
2020-08-17 06:05:16 +00:00
|
|
|
{
|
|
|
|
// Offline info
|
|
|
|
if (Playing())
|
|
|
|
discordPresence.state = "Offline";
|
|
|
|
else if (demo.playback && !demo.title)
|
|
|
|
discordPresence.state = "Watching Replay";
|
|
|
|
else
|
|
|
|
discordPresence.state = "Menu";
|
|
|
|
}
|
2018-10-30 09:44:29 +00:00
|
|
|
|
|
|
|
// Gametype info
|
|
|
|
if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING)
|
|
|
|
{
|
|
|
|
if (modeattacking)
|
2020-08-17 06:25:05 +00:00
|
|
|
discordPresence.details = "Time Attack";
|
2018-10-30 09:44:29 +00:00
|
|
|
else
|
|
|
|
discordPresence.details = gametype_cons_t[gametype].strvalue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) // Map info
|
|
|
|
{
|
2020-08-17 04:29:52 +00:00
|
|
|
if ((gamemap >= 1 && gamemap <= 60) // supported race maps
|
|
|
|
|| (gamemap >= 136 && gamemap <= 164)) // supported battle maps
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
|
|
|
snprintf(mapimg, 8, "%s", G_BuildMapName(gamemap));
|
|
|
|
strlwr(mapimg);
|
|
|
|
discordPresence.largeImageKey = mapimg; // Map image
|
|
|
|
}
|
2020-08-17 08:42:22 +00:00
|
|
|
else if (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU)
|
|
|
|
{
|
|
|
|
// Hell map, use the method that got you here :P
|
|
|
|
discordPresence.largeImageKey = "maphell";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This is probably a custom map!
|
|
|
|
discordPresence.largeImageKey = "mapcustom";
|
|
|
|
}
|
2018-10-30 09:44:29 +00:00
|
|
|
|
2020-08-17 08:42:22 +00:00
|
|
|
if (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU)
|
|
|
|
{
|
|
|
|
// Hell map, hide the name
|
2018-10-30 09:44:29 +00:00
|
|
|
discordPresence.largeImageText = "Map: ???";
|
2020-08-17 08:42:22 +00:00
|
|
|
}
|
2018-10-30 09:44:29 +00:00
|
|
|
else
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
2018-10-30 09:44:29 +00:00
|
|
|
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) : "");
|
|
|
|
discordPresence.largeImageText = mapname; // Map name
|
2018-10-22 04:34:45 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 10:13:32 +00:00
|
|
|
if (Playing())
|
2020-08-17 06:05:16 +00:00
|
|
|
{
|
|
|
|
const time_t currentTime = time(NULL);
|
2020-08-17 10:13:32 +00:00
|
|
|
const time_t mapTimeStart = currentTime - ((leveltime + (modeattacking ? starttime : 0)) / TICRATE);
|
2020-08-17 06:05:16 +00:00
|
|
|
|
|
|
|
discordPresence.startTimestamp = mapTimeStart;
|
|
|
|
|
|
|
|
if (timelimitintics > 0)
|
|
|
|
{
|
|
|
|
const time_t mapTimeEnd = mapTimeStart + ((timelimitintics + starttime + 1) / TICRATE);
|
|
|
|
discordPresence.endTimestamp = mapTimeEnd;
|
|
|
|
}
|
|
|
|
}
|
2018-10-30 09:44:29 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2020-08-17 08:42:05 +00:00
|
|
|
// 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)
|
2018-10-22 04:34:45 +00:00
|
|
|
{
|
2020-08-17 08:42:05 +00:00
|
|
|
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";
|
2018-10-22 04:34:45 +00:00
|
|
|
}
|
2018-10-30 09:44:29 +00:00
|
|
|
|
|
|
|
snprintf(charname, 28, "Character: %s", skins[players[consoleplayer].skin].realname);
|
|
|
|
discordPresence.smallImageText = charname; // Character name
|
2018-10-22 04:34:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Discord_UpdatePresence(&discordPresence);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|