rpc and shi
This commit is contained in:
@@ -297,6 +297,14 @@
|
||||
</Unit>
|
||||
<Unit filename="../src/crypto.cpp" />
|
||||
<Unit filename="../src/crypto_tools.h" />
|
||||
<Unit filename="../src/discordrpc.cpp">
|
||||
<Option target="default" />
|
||||
<Option target="debug" />
|
||||
</Unit>
|
||||
<Unit filename="../src/discordrpc.h">
|
||||
<Option target="default" />
|
||||
<Option target="debug" />
|
||||
</Unit>
|
||||
<Unit filename="../src/cube.h">
|
||||
<Option compile="1" />
|
||||
<Option weight="0" />
|
||||
|
||||
@@ -259,6 +259,8 @@
|
||||
<Unit filename="../src/soundscheduler.cpp" />
|
||||
<Unit filename="../src/renderhud.cpp" />
|
||||
<Unit filename="../src/console.cpp" />
|
||||
<Unit filename="../src/discordrpc.cpp" />
|
||||
<Unit filename="../src/discordrpc.h" />
|
||||
<Unit filename="../src/editing.cpp" />
|
||||
<Unit filename="../src/bot/ac_bot_ai.cpp" />
|
||||
<Unit filename="../src/bot/ac_bot.cpp" />
|
||||
|
||||
@@ -73,6 +73,7 @@ CLIENT_OBJS= \
|
||||
clients2c.o \
|
||||
command.o \
|
||||
console.o \
|
||||
discordrpc.o \
|
||||
docs.o \
|
||||
editing.o \
|
||||
entities.o \
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "cube.h"
|
||||
#include "bot/bot.h"
|
||||
#include "discordrpc.h"
|
||||
|
||||
VAR(connected, 1, 0, 0);
|
||||
|
||||
@@ -293,6 +294,7 @@ COMMAND(current_version, "s");
|
||||
|
||||
void cleanupclient()
|
||||
{
|
||||
discordrpccleanup();
|
||||
abortconnect();
|
||||
disconnect(1);
|
||||
if(clienthost)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "cube.h"
|
||||
#include "bot/bot.h"
|
||||
#include "discordrpc.h"
|
||||
|
||||
int nextmode = 0; // nextmode becomes gamemode after next map load
|
||||
VAR(gamemode, 1, 0, 0);
|
||||
@@ -1898,6 +1899,40 @@ void spectatecn()
|
||||
}
|
||||
COMMAND(spectatecn, "");
|
||||
|
||||
// Discord Rich Presence: shows what we're doing on the user's discord profile
|
||||
|
||||
VARP(discordrpc, 0, 1, 1);
|
||||
|
||||
void updategamepresence(int millis)
|
||||
{
|
||||
if(!discordrpc)
|
||||
{
|
||||
discordrpctoggle(false);
|
||||
return;
|
||||
}
|
||||
discordrpctoggle(true);
|
||||
|
||||
static int lastbuild = 0;
|
||||
if(!lastbuild || millis - lastbuild >= 1000) // the details don't change every frame
|
||||
{
|
||||
lastbuild = millis;
|
||||
string details, state;
|
||||
serverinfo *si = multiplayer(NULL) ? getconnectedserverinfo() : NULL;
|
||||
const char *map = clientmap[0] ? clientmap : "unknown map";
|
||||
|
||||
if(multiplayer(NULL) && si && si->name[0]) copystring(details, si->name);
|
||||
else if(multiplayer(NULL)) copystring(details, "Multiplayer game");
|
||||
else copystring(details, "Singleplayer");
|
||||
|
||||
if(si && si->numplayers > 0) formatstring(state)("%s on %s (%d/%d players)", modestr(gamemode, true), map, si->numplayers, si->maxclients);
|
||||
else formatstring(state)("%s on %s", modestr(gamemode, true), map);
|
||||
|
||||
discordrpcpresence(details, state);
|
||||
}
|
||||
|
||||
discordrpcpoll(millis);
|
||||
}
|
||||
|
||||
void serverextension(char *ext, char *args)
|
||||
{
|
||||
if(!ext || !ext[0]) return;
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
// discordrpc.cpp: minimal Discord Rich Presence client.
|
||||
//
|
||||
// Talks the Discord desktop client's local IPC protocol directly over its unix
|
||||
// socket / windows named pipe (the same thing the official SDK does), so there's
|
||||
// no library to link and no helper process to ship. Everything is non-blocking
|
||||
// and non-fatal: if Discord isn't running we simply retry now and then.
|
||||
|
||||
#include "discordrpc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
|
||||
static const char *RPC_CLIENT_ID = "1564063539117826158";
|
||||
|
||||
// IPC opcodes and frame layout: uint32 opcode, uint32 length, then the JSON payload
|
||||
enum { RPC_OP_HANDSHAKE = 0, RPC_OP_FRAME, RPC_OP_CLOSE, RPC_OP_PING, RPC_OP_PONG };
|
||||
|
||||
enum
|
||||
{
|
||||
RPC_RETRY_INTERVAL = 5000, // how often to look for Discord while it isn't there
|
||||
RPC_SEND_INTERVAL = 5000, // Discord allows 5 activity updates per 20 seconds
|
||||
RPC_MAX_PAYLOAD = 4096,
|
||||
RPC_MAX_FIELD = 128, // Discord's limit for details/state
|
||||
RPC_INBUF_SIZE = 8192
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef HANDLE rpcsocket;
|
||||
#define RPC_NOSOCKET (INVALID_HANDLE_VALUE)
|
||||
#else
|
||||
typedef int rpcsocket;
|
||||
#define RPC_NOSOCKET (-1)
|
||||
#endif
|
||||
|
||||
static rpcsocket rpcsock = RPC_NOSOCKET;
|
||||
static bool rpcwanted = false, rpcready = false;
|
||||
static int rpclasttry = -1, rpclastsend = -1;
|
||||
static int rpcinlen = 0, rpcnonce = 0;
|
||||
static time_t rpcstart = 0;
|
||||
static char rpcinbuf[RPC_INBUF_SIZE];
|
||||
static char rpcdetails[RPC_MAX_FIELD], rpcstate[RPC_MAX_FIELD];
|
||||
static char rpcsentdetails[RPC_MAX_FIELD], rpcsentstate[RPC_MAX_FIELD];
|
||||
|
||||
static int rpcpid()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (int)GetCurrentProcessId();
|
||||
#else
|
||||
return (int)getpid();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
static void rpcdisconnect()
|
||||
{
|
||||
if(rpcsock == RPC_NOSOCKET) return;
|
||||
#ifdef _WIN32
|
||||
CloseHandle(rpcsock);
|
||||
#else
|
||||
close(rpcsock);
|
||||
#endif
|
||||
rpcsock = RPC_NOSOCKET;
|
||||
rpcready = false;
|
||||
rpcinlen = 0;
|
||||
// forget what Discord has, so the activity is pushed again after a reconnect
|
||||
rpcsentdetails[0] = rpcsentstate[0] = 0;
|
||||
}
|
||||
|
||||
static bool rpcwrite(int op, const char *payload, int len);
|
||||
|
||||
// tells Discord to drop our activity instead of leaving the last one stuck there
|
||||
static void rpcclearactivity()
|
||||
{
|
||||
static const char *clearmessage = "{\"cmd\":\"SET_ACTIVITY\",\"args\":{\"pid\":0,\"activity\":null}}";
|
||||
if(rpcready) rpcwrite(RPC_OP_FRAME, clearmessage, (int)strlen(clearmessage));
|
||||
}
|
||||
|
||||
static bool rpcwrite(int op, const char *payload, int len)
|
||||
{
|
||||
if(rpcsock == RPC_NOSOCKET) return false;
|
||||
if(len < 0 || len > RPC_MAX_PAYLOAD) return false;
|
||||
|
||||
char buf[8 + RPC_MAX_PAYLOAD];
|
||||
buf[0] = (char)(op&0xff);
|
||||
buf[1] = (char)((op>>8)&0xff);
|
||||
buf[2] = buf[3] = 0;
|
||||
buf[4] = (char)(len&0xff);
|
||||
buf[5] = (char)((len>>8)&0xff);
|
||||
buf[6] = (char)((len>>16)&0xff);
|
||||
buf[7] = (char)((len>>24)&0xff);
|
||||
memcpy(buf + 8, payload, len);
|
||||
|
||||
int total = 8 + len, sent = 0;
|
||||
while(sent < total)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD written = 0;
|
||||
if(!WriteFile(rpcsock, buf + sent, (DWORD)(total - sent), &written, NULL) || !written)
|
||||
{
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
sent += (int)written;
|
||||
#else
|
||||
int n = (int)send(rpcsock, buf + sent, total - sent, MSG_NOSIGNAL);
|
||||
if(n < 0)
|
||||
{
|
||||
if(errno == EINTR) continue;
|
||||
// full pipe buffer: drop this update instead of stalling the game
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK) return false;
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
if(!n)
|
||||
{
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
sent += n;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// reads everything Discord has sent us so far, false if the pipe died
|
||||
static bool rpcreceive()
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
if(rpcinlen >= RPC_INBUF_SIZE)
|
||||
{
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
DWORD available = 0;
|
||||
if(!PeekNamedPipe(rpcsock, NULL, 0, NULL, &available, NULL))
|
||||
{
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
if(!available) return true;
|
||||
DWORD got = 0;
|
||||
if(!ReadFile(rpcsock, rpcinbuf + rpcinlen, (DWORD)(RPC_INBUF_SIZE - rpcinlen), &got, NULL))
|
||||
{
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
if(!got)
|
||||
{
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
rpcinlen += (int)got;
|
||||
#else
|
||||
int got = (int)recv(rpcsock, rpcinbuf + rpcinlen, RPC_INBUF_SIZE - rpcinlen, 0);
|
||||
if(got < 0)
|
||||
{
|
||||
if(errno == EINTR) continue;
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK) return true;
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
if(!got)
|
||||
{
|
||||
// Discord closed the pipe (client quit or restarted)
|
||||
rpcdisconnect();
|
||||
return false;
|
||||
}
|
||||
rpcinlen += got;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void rpchandlemessage(int op, const char *payload, int len)
|
||||
{
|
||||
if(op == RPC_OP_PING) { rpcwrite(RPC_OP_PONG, payload, len); return; }
|
||||
if(op == RPC_OP_CLOSE) { rpcdisconnect(); return; }
|
||||
if(op != RPC_OP_FRAME) return;
|
||||
|
||||
char msg[RPC_MAX_PAYLOAD + 1];
|
||||
if(len > RPC_MAX_PAYLOAD) len = RPC_MAX_PAYLOAD;
|
||||
memcpy(msg, payload, len);
|
||||
msg[len] = 0;
|
||||
|
||||
if(!rpcready)
|
||||
{
|
||||
if(strstr(msg, "\"evt\":\"READY\"") || strstr(msg, "\"evt\": \"READY\""))
|
||||
{
|
||||
rpcready = true;
|
||||
rpclastsend = -1; // push the current activity straight away
|
||||
}
|
||||
// a bad/expired client id or a rejected handshake: stop retrying this pipe
|
||||
else if(strstr(msg, "\"evt\":\"ERROR\"") || strstr(msg, "\"evt\": \"ERROR\"")) rpcdisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
static void rpcprocess()
|
||||
{
|
||||
int pos = 0;
|
||||
while(rpcinlen - pos >= 8)
|
||||
{
|
||||
const unsigned char *h = (const unsigned char *)rpcinbuf + pos;
|
||||
int op = h[0] | (h[1]<<8) | (h[2]<<16) | (h[3]<<24);
|
||||
int len = h[4] | (h[5]<<8) | (h[6]<<16) | (h[7]<<24);
|
||||
if(len < 0 || len > RPC_INBUF_SIZE - 8)
|
||||
{
|
||||
rpcdisconnect();
|
||||
return;
|
||||
}
|
||||
if(rpcinlen - pos - 8 < len) break; // partial frame, wait for more
|
||||
rpchandlemessage(op, rpcinbuf + pos + 8, len);
|
||||
if(rpcsock == RPC_NOSOCKET) return;
|
||||
pos += 8 + len;
|
||||
}
|
||||
if(pos)
|
||||
{
|
||||
memmove(rpcinbuf, rpcinbuf + pos, rpcinlen - pos);
|
||||
rpcinlen -= pos;
|
||||
}
|
||||
}
|
||||
|
||||
static void rpcappend(char *buf, int max, int &len, const char *s)
|
||||
{
|
||||
int l = (int)strlen(s);
|
||||
if(len + l > max - 1) l = max - 1 - len;
|
||||
if(l <= 0) return;
|
||||
memcpy(buf + len, s, l);
|
||||
len += l;
|
||||
buf[len] = 0;
|
||||
}
|
||||
|
||||
static void rpcappendstring(char *buf, int max, int &len, const char *s)
|
||||
{
|
||||
rpcappend(buf, max, len, "\"");
|
||||
for(const char *p = s; *p; p++)
|
||||
{
|
||||
unsigned char c = (unsigned char)*p;
|
||||
if(c == '"' || c == '\\')
|
||||
{
|
||||
char esc[3] = { '\\', (char)c, 0 };
|
||||
rpcappend(buf, max, len, esc);
|
||||
}
|
||||
else if(c < 0x20)
|
||||
{
|
||||
char esc[8];
|
||||
snprintf(esc, sizeof(esc), "\\u%04x", c);
|
||||
rpcappend(buf, max, len, esc);
|
||||
}
|
||||
else
|
||||
{
|
||||
char chr[2] = { (char)c, 0 };
|
||||
rpcappend(buf, max, len, chr);
|
||||
}
|
||||
}
|
||||
rpcappend(buf, max, len, "\"");
|
||||
}
|
||||
|
||||
static void rpcsendactivity()
|
||||
{
|
||||
if(!rpcready) return;
|
||||
if(!rpcstart) rpcstart = time(NULL);
|
||||
|
||||
char activity[768];
|
||||
int len = 0;
|
||||
activity[0] = 0;
|
||||
if(rpcdetails[0])
|
||||
{
|
||||
rpcappend(activity, sizeof(activity), len, "\"details\":");
|
||||
rpcappendstring(activity, sizeof(activity), len, rpcdetails);
|
||||
}
|
||||
if(rpcstate[0])
|
||||
{
|
||||
if(len) rpcappend(activity, sizeof(activity), len, ",");
|
||||
rpcappend(activity, sizeof(activity), len, "\"state\":");
|
||||
rpcappendstring(activity, sizeof(activity), len, rpcstate);
|
||||
}
|
||||
if(len) rpcappend(activity, sizeof(activity), len, ",");
|
||||
char timestamps[64];
|
||||
snprintf(timestamps, sizeof(timestamps), "\"timestamps\":{\"start\":%ld}", (long)rpcstart);
|
||||
rpcappend(activity, sizeof(activity), len, timestamps);
|
||||
|
||||
char nonce[64];
|
||||
rpcnonce++;
|
||||
snprintf(nonce, sizeof(nonce), "%lx-%x-%x-%lx",
|
||||
(unsigned long)time(NULL), (unsigned)(rpcpid()&0xffff),
|
||||
(unsigned)(rpcnonce&0xffff),
|
||||
(unsigned long)((rpcnonce * 2654435761UL)&0xffffffffUL));
|
||||
|
||||
char msg[1024];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"{\"cmd\":\"SET_ACTIVITY\",\"nonce\":\"%s\",\"args\":{\"pid\":%d,\"activity\":{%s}}}",
|
||||
nonce, rpcpid(), activity);
|
||||
|
||||
if(rpcwrite(RPC_OP_FRAME, msg, (int)strlen(msg)))
|
||||
{
|
||||
memcpy(rpcsentdetails, rpcdetails, RPC_MAX_FIELD);
|
||||
memcpy(rpcsentstate, rpcstate, RPC_MAX_FIELD);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
static bool rpcconnectsocket(const char *path)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
if(strlen(path) >= sizeof(addr.sun_path)) return false;
|
||||
memcpy(addr.sun_path, path, strlen(path) + 1);
|
||||
|
||||
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if(s < 0) return false;
|
||||
if(connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
||||
{
|
||||
close(s);
|
||||
return false;
|
||||
}
|
||||
// never let a wedged Discord stall the game
|
||||
fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK);
|
||||
#ifdef SO_NOSIGPIPE
|
||||
int on = 1;
|
||||
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
|
||||
#endif
|
||||
rpcsock = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool rpcconnectdir(const char *dir)
|
||||
{
|
||||
if(!dir || !*dir) return false;
|
||||
char path[256];
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
snprintf(path, sizeof(path), "%s/discord-ipc-%d", dir, i);
|
||||
if(rpcconnectsocket(path)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void rpcconnect()
|
||||
{
|
||||
rpcdisconnect();
|
||||
|
||||
// the usual places for the discord-ipc-N socket: plain, flatpak and snap installs
|
||||
const char *runtime = getenv("XDG_RUNTIME_DIR");
|
||||
char sub[256];
|
||||
if(runtime && *runtime)
|
||||
{
|
||||
if(rpcconnectdir(runtime)) return;
|
||||
snprintf(sub, sizeof(sub), "%s/app/com.discordapp.Discord", runtime);
|
||||
if(rpcconnectdir(sub)) return;
|
||||
snprintf(sub, sizeof(sub), "%s/app/com.discordapp.DiscordCanary", runtime);
|
||||
if(rpcconnectdir(sub)) return;
|
||||
snprintf(sub, sizeof(sub), "%s/app/com.discordapp.DiscordPTB", runtime);
|
||||
if(rpcconnectdir(sub)) return;
|
||||
snprintf(sub, sizeof(sub), "%s/.flatpak/com.discordapp.Discord/xdg-run", runtime);
|
||||
if(rpcconnectdir(sub)) return;
|
||||
snprintf(sub, sizeof(sub), "%s/snap.discord", runtime);
|
||||
if(rpcconnectdir(sub)) return;
|
||||
}
|
||||
const char *tmp = getenv("TMPDIR");
|
||||
if(rpcconnectdir(tmp)) return;
|
||||
if(rpcconnectdir("/tmp")) return;
|
||||
if(rpcconnectdir("/tmp/snap.discord")) return;
|
||||
return;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void rpcconnect()
|
||||
{
|
||||
rpcdisconnect();
|
||||
char path[64];
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
snprintf(path, sizeof(path), "\\\\?\\pipe\\discord-ipc-%d", i);
|
||||
HANDLE h = CreateFileA(path, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if(h != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
rpcsock = h;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void rpchandshake()
|
||||
{
|
||||
rpcready = false;
|
||||
rpcinlen = 0;
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "{\"v\":1,\"client_id\":\"%s\"}", RPC_CLIENT_ID);
|
||||
if(!rpcwrite(RPC_OP_HANDSHAKE, msg, (int)strlen(msg))) rpcdisconnect();
|
||||
}
|
||||
|
||||
void discordrpcpresence(const char *details, const char *state)
|
||||
{
|
||||
const char *d = details ? details : "", *s = state ? state : "";
|
||||
if(!strcmp(d, rpcdetails) && !strcmp(s, rpcstate)) return;
|
||||
snprintf(rpcdetails, RPC_MAX_FIELD, "%s", d);
|
||||
snprintf(rpcstate, RPC_MAX_FIELD, "%s", s);
|
||||
}
|
||||
|
||||
void discordrpctoggle(bool enabled)
|
||||
{
|
||||
if(enabled == rpcwanted) return;
|
||||
rpcwanted = enabled;
|
||||
if(!enabled)
|
||||
{
|
||||
rpcclearactivity();
|
||||
rpcdisconnect();
|
||||
rpcstart = 0;
|
||||
rpclastsend = rpclasttry = -1;
|
||||
}
|
||||
else rpclasttry = -1; // try to connect on the next poll
|
||||
}
|
||||
|
||||
void discordrpcpoll(int millis)
|
||||
{
|
||||
if(!rpcwanted)
|
||||
{
|
||||
rpcdisconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if(rpcsock == RPC_NOSOCKET)
|
||||
{
|
||||
if(rpclasttry >= 0 && millis - rpclasttry >= 0 && millis - rpclasttry < RPC_RETRY_INTERVAL) return;
|
||||
rpclasttry = millis;
|
||||
rpcconnect();
|
||||
if(rpcsock == RPC_NOSOCKET) return;
|
||||
rpchandshake();
|
||||
if(rpcsock == RPC_NOSOCKET) return;
|
||||
}
|
||||
|
||||
if(!rpcreceive()) return;
|
||||
rpcprocess();
|
||||
if(!rpcready || rpcsock == RPC_NOSOCKET) return;
|
||||
|
||||
if(rpclastsend >= 0 && millis - rpclastsend >= 0 && millis - rpclastsend < RPC_SEND_INTERVAL) return;
|
||||
if(!strcmp(rpcdetails, rpcsentdetails) && !strcmp(rpcstate, rpcsentstate)) return;
|
||||
rpclastsend = millis;
|
||||
rpcsendactivity();
|
||||
}
|
||||
|
||||
void discordrpccleanup()
|
||||
{
|
||||
rpcclearactivity();
|
||||
rpcdisconnect();
|
||||
rpcwanted = false;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// discordrpc.h: minimal Discord Rich Presence client (no external dependencies)
|
||||
|
||||
#ifndef DISCORDRPC_H
|
||||
#define DISCORDRPC_H
|
||||
|
||||
// sets the activity shown on Discord, NULL or "" clears it. Cheap to call every
|
||||
// frame: nothing is sent to Discord unless the text actually changed.
|
||||
extern void discordrpcpresence(const char *details, const char *state);
|
||||
|
||||
// enables/disables the connection (disabling also clears the activity)
|
||||
extern void discordrpctoggle(bool enabled);
|
||||
|
||||
// drive from the main loop: (re)connects, answers pings, pushes changed activity.
|
||||
// millis is a monotonic millisecond clock (the game's totalmillis).
|
||||
extern void discordrpcpoll(int millis);
|
||||
|
||||
// closes the connection and clears the activity (call on exit)
|
||||
extern void discordrpccleanup();
|
||||
|
||||
#endif
|
||||
@@ -1552,6 +1552,8 @@ int main(int argc, char **argv)
|
||||
|
||||
if(needsautoscreenshot) showscores(true);
|
||||
|
||||
updategamepresence(totalmillis);
|
||||
|
||||
serverslice(0);
|
||||
|
||||
if(elapsed) fps = (1000.0f/elapsed+fps*10)/11; // avoid DIV-by-0
|
||||
|
||||
@@ -521,6 +521,7 @@ extern void resetwater();
|
||||
extern void abortconnect();
|
||||
extern void disconnect(int onlyclean = 0, int async = 0);
|
||||
extern void cleanupclient();
|
||||
extern void updategamepresence(int millis);
|
||||
extern void toserver(char *text);
|
||||
extern void addmsg(int type, const char *fmt = NULL, ...);
|
||||
extern bool multiplayer(const char *op = "");
|
||||
|
||||
@@ -644,6 +644,13 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone|Win32'">true</ExcludedFromBuild>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Standalone|Win32'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\discordrpc.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone|Win32'">true</ExcludedFromBuild>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Profile|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\crypto.cpp" />
|
||||
<ClCompile Include="..\src\docs.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Standalone Debug|Win32'">true</ExcludedFromBuild>
|
||||
|
||||
@@ -314,6 +314,9 @@
|
||||
<ClCompile Include="..\src\console.cpp">
|
||||
<Filter>support</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\discordrpc.cpp">
|
||||
<Filter>support</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\crypto.cpp">
|
||||
<Filter>support</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
B914CEAC11CAC42F009BA307 /* packetqueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE5D11CAC42F009BA307 /* packetqueue.cpp */; };
|
||||
B914CEAD11CAC42F009BA307 /* pch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE5E11CAC42F009BA307 /* pch.cpp */; };
|
||||
B914CEAE11CAC42F009BA307 /* physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE5F11CAC42F009BA307 /* physics.cpp */; };
|
||||
B9D15C0211CAC42F009BA307 /* discordrpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9D15C0111CAC42F009BA307 /* discordrpc.cpp */; };
|
||||
B914CEB011CAC42F009BA307 /* protocol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE6111CAC42F009BA307 /* protocol.cpp */; };
|
||||
B914CEB311CAC42F009BA307 /* rendercubes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE6411CAC42F009BA307 /* rendercubes.cpp */; };
|
||||
B914CEB411CAC42F009BA307 /* rendergl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B914CE6511CAC42F009BA307 /* rendergl.cpp */; };
|
||||
@@ -193,6 +194,8 @@
|
||||
B914CE5D11CAC42F009BA307 /* packetqueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = packetqueue.cpp; sourceTree = "<group>"; };
|
||||
B914CE5E11CAC42F009BA307 /* pch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pch.cpp; sourceTree = "<group>"; };
|
||||
B914CE5F11CAC42F009BA307 /* physics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = physics.cpp; sourceTree = "<group>"; };
|
||||
B9D15C0111CAC42F009BA307 /* discordrpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = discordrpc.cpp; sourceTree = "<group>"; };
|
||||
B9D15C0311CAC42F009BA307 /* discordrpc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = discordrpc.h; sourceTree = "<group>"; };
|
||||
B914CE6011CAC42F009BA307 /* platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform.h; sourceTree = "<group>"; };
|
||||
B914CE6111CAC42F009BA307 /* protocol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = protocol.cpp; sourceTree = "<group>"; };
|
||||
B914CE6211CAC42F009BA307 /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protocol.h; sourceTree = "<group>"; };
|
||||
@@ -418,6 +421,8 @@
|
||||
B914CE5D11CAC42F009BA307 /* packetqueue.cpp */,
|
||||
B914CE5E11CAC42F009BA307 /* pch.cpp */,
|
||||
B914CE5F11CAC42F009BA307 /* physics.cpp */,
|
||||
B9D15C0111CAC42F009BA307 /* discordrpc.cpp */,
|
||||
B9D15C0311CAC42F009BA307 /* discordrpc.h */,
|
||||
B914CE6011CAC42F009BA307 /* platform.h */,
|
||||
B914CE6111CAC42F009BA307 /* protocol.cpp */,
|
||||
B914CE6211CAC42F009BA307 /* protocol.h */,
|
||||
@@ -640,6 +645,7 @@
|
||||
B914CEAC11CAC42F009BA307 /* packetqueue.cpp in Sources */,
|
||||
B914CEAD11CAC42F009BA307 /* pch.cpp in Sources */,
|
||||
B914CEAE11CAC42F009BA307 /* physics.cpp in Sources */,
|
||||
B9D15C0211CAC42F009BA307 /* discordrpc.cpp in Sources */,
|
||||
B914CEB011CAC42F009BA307 /* protocol.cpp in Sources */,
|
||||
B914CEB311CAC42F009BA307 /* rendercubes.cpp in Sources */,
|
||||
B914CEB411CAC42F009BA307 /* rendergl.cpp in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user