From a30d58660b82e55a2a0452b42739149dc593b2b0 Mon Sep 17 00:00:00 2001 From: John FrostFox <john.frostfox@gmail.com> Date: Sun, 14 Nov 2021 23:03:56 +0300 Subject: [PATCH] New Feature: Discord RPC integration --- .drone.yml | 4 + .../win32-dynamic/include/discord_register.h | 26 + .../win32-dynamic/include/discord_rpc.h | 87 ++ .../win32-dynamic/lib/discord-rpc.lib | Bin 0 -> 3656 bytes .../win64-dynamic/include/discord_register.h | 26 + .../win64-dynamic/include/discord_rpc.h | 87 ++ .../win64-dynamic/lib/discord-rpc.lib | Bin 0 -> 3622 bytes src/CMakeLists.txt | 28 + src/Makefile | 3 + src/Makefile.d/features.mk | 6 + src/Makefile.d/win32.mk | 11 + src/Sourcefile | 1 - src/d_clisrv.c | 45 +- src/d_clisrv.h | 11 +- src/d_main.c | 16 + src/d_netcmd.c | 61 ++ src/d_netcmd.h | 1 + src/discord.c | 744 ++++++++++++++++++ src/discord.h | 81 ++ src/doomdef.h | 11 + src/g_game.c | 7 + src/i_tcp.c | 9 +- src/m_menu.c | 301 ++++++- src/m_menu.h | 2 + src/m_swap.h | 40 +- src/mserv.c | 10 +- src/sdl/CMakeLists.txt | 8 + src/sdl/i_system.c | 6 +- src/sdl/i_video.c | 9 + src/sounds.c | 6 + src/sounds.h | 6 + src/st_stuff.c | 26 + src/st_stuff.h | 5 + src/stun.c | 233 ++++++ src/stun.h | 20 + src/v_video.c | 4 + 36 files changed, 1901 insertions(+), 40 deletions(-) create mode 100644 libs/discord-rpc/win32-dynamic/include/discord_register.h create mode 100644 libs/discord-rpc/win32-dynamic/include/discord_rpc.h create mode 100644 libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib create mode 100644 libs/discord-rpc/win64-dynamic/include/discord_register.h create mode 100644 libs/discord-rpc/win64-dynamic/include/discord_rpc.h create mode 100644 libs/discord-rpc/win64-dynamic/lib/discord-rpc.lib create mode 100644 src/discord.c create mode 100644 src/discord.h create mode 100644 src/stun.c create mode 100644 src/stun.h diff --git a/.drone.yml b/.drone.yml index c374f766c..b17c114d2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -51,11 +51,13 @@ steps: commands: - pacman -Syu libgme libopenmpt libpng sdl2_mixer glu mesa nasm upx git mingw-w64-binutils mingw-w64-crt mingw-w64-gcc mingw-w64-headers mingw-w64-winpthreads --noconfirm - env PREFIX=i686-w64-mingw32 make -C src/ NOUPX=1 MINGW=1 EXENAME=srb2win_netplus.exe -j $(grep -c processor /proc/cpuinfo) + - env PREFIX=i686-w64-mingw32 make -C src/ NOUPX=1 MINGW=1 HAVE_DISCORDRPC=1 EXENAME=srb2win_netplus_discordrpc.exe -j $(grep -c processor /proc/cpuinfo) - name: publish image: vividboarder/drone-webdav settings: file: bin/srb2win_netplus.exe + file: bin/srb2win_netplus_discordrpc.exe destination: from_secret: upload_destination_windowsx86 username: @@ -81,11 +83,13 @@ steps: commands: - pacman -Syu libgme libopenmpt libpng sdl2_mixer glu mesa nasm upx git mingw-w64-binutils mingw-w64-crt mingw-w64-gcc mingw-w64-headers mingw-w64-winpthreads --noconfirm - env PREFIX=x86_64-w64-mingw32 make -C src/ MINGW64=1 NOUPX=1 EXENAME=srb2win_x64_netplus.exe -j $(grep -c processor /proc/cpuinfo) + - env PREFIX=x86_64-w64-mingw32 make -C src/ MINGW64=1 NOUPX=1 HAVE_DISCORDRPC=1 EXENAME=srb2win_x64_netplus_discordrpc.exe -j $(grep -c processor /proc/cpuinfo) - name: publish image: vividboarder/drone-webdav settings: file: bin/srb2win_x64_netplus.exe + file: bin/srb2win_x64_netplus_discordrpc.exe destination: from_secret: upload_destination_windowsx64 username: diff --git a/libs/discord-rpc/win32-dynamic/include/discord_register.h b/libs/discord-rpc/win32-dynamic/include/discord_register.h new file mode 100644 index 000000000..16fb42f32 --- /dev/null +++ b/libs/discord-rpc/win32-dynamic/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/libs/discord-rpc/win32-dynamic/include/discord_rpc.h b/libs/discord-rpc/win32-dynamic/include/discord_rpc.h new file mode 100644 index 000000000..3e1441e05 --- /dev/null +++ b/libs/discord-rpc/win32-dynamic/include/discord_rpc.h @@ -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 diff --git a/libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib b/libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib new file mode 100644 index 0000000000000000000000000000000000000000..d8b6689f3ca2cdf5fb142b0b7b9752cdb46fc903 GIT binary patch literal 3656 zcmY$iNi0gvu;bEKKm~@TCdS6bmWGxlsNx1tu9+cBB7uv6fkBjkfi;YQfqeo41IH-_ z2Cg{}%=3(a0gMY67<dX87<ey0FqaAg0|;~fV_*Pb-UtQ;5at$OU;yJL1_th?_;^p> z0RNznco)}T=OE935dWa~l+5Df{Gt@yqJm_G_;|liA0JFb4E0E=AZkK9LjAnsT^vIk z8RA_aM#OvOWtL<n=44i-GQ`Jc<`%?bm4T@a&L}NO$uG}CSBnsbsSGO3b56|3NlHx4 zE=E_4DhJaMT9A@hk{VEyTAZ4fjNuw|S(q;8oYcf3T$<1%VS0j6iwpAeQZSqa6NagS z*y)j&my(lOgy9QxS(vV%)bz~alGGwh<6+|HDuYW>6LZ}Yb1^&(GYX-A<P>UzNTECo z3=Cnc3=9+485mA+Ffh#FVqkd2!@y9$!@zKXmw`cri-F-EHv>ZiF9U-BHv>ZxH#kW% za4@hia5L~Qa5AtlurhElurn|+Fkum8Mx-hn0gs&6&;tRX8PnJBGzpJ6xNekW1{1|h zj<_NVB94@P$joeLc?Zq^xHA^249w@ak`5vrpxFlx7c}!wLI+h25k{!ukPyM2=;7go zw~Rq5fAmsva`Xw6rltm#&~i0_%ZHJfVOP!HXkkVM1`Y-=VAM-VO)5=S2?p~R7#Lg` z7#MnB94PI;AmF5znUYwNsA2>aVh~|qV7SP@!0;3*z|6qF;J{#T0IJRmD$F3jz`(%F zh)@R-cVJLBz{J47AjZH6;xh1~c}kr@Sb!a*4=l^Tz{kMAFg;GlH#4~?zc@dwL_s4+ zQ^Cj6&l@U_LV+?Q$T1)T7#LU>7#P^Wj$j3|&}?FGNY2kK(92Aj9>>V||33o*NGC)W z$WV|YnHZp6GIC&G06RMaCIa>(NCz`a!~~=Uq!X^j6d?i<R{;@F3^JX8!4}E`QA`X` z3=9mkshqgbyiPFX7?~nUIUhzQaLU=sz`y`;HUrj_lLGPu5=Kfn#xP+}%2^5HKxt6Q z0hvpa6hTZJ%YwrlN2O14$XS?S<hh#!LJm)!Gld2cI5C6r9E=a5K_LfCpe7(b2!j&+ zRS*LSgVa$qgoz1NMFs}^EhbV!))ZElDS+CBpbYE72rALk!9L|cRClO#1)4PsB<BTF zScIb43(C%n3`q7ORmSL+qVWmlKX~>-vr`G2l923#S8!-HFre`X+Gu7BO}1z@YJ<yS zEH)ylPBg2?2w@y{gOU&natOm}Wy)=|!0uZ$1_lOJ6dRE%d~|Ek_yofkU#zkr+X`<= lpxHortin20XtAmRt`L!86|Ip%OM5wxeG0J`-uywg7XXI8n@<1$ literal 0 HcmV?d00001 diff --git a/libs/discord-rpc/win64-dynamic/include/discord_register.h b/libs/discord-rpc/win64-dynamic/include/discord_register.h new file mode 100644 index 000000000..16fb42f32 --- /dev/null +++ b/libs/discord-rpc/win64-dynamic/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/libs/discord-rpc/win64-dynamic/include/discord_rpc.h b/libs/discord-rpc/win64-dynamic/include/discord_rpc.h new file mode 100644 index 000000000..3e1441e05 --- /dev/null +++ b/libs/discord-rpc/win64-dynamic/include/discord_rpc.h @@ -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 diff --git a/libs/discord-rpc/win64-dynamic/lib/discord-rpc.lib b/libs/discord-rpc/win64-dynamic/lib/discord-rpc.lib new file mode 100644 index 0000000000000000000000000000000000000000..fcd009d82f6353ab287f3b5fa1b8d0f8416e82bf GIT binary patch literal 3622 zcmY$iNi0gvu;bEKKm~@TCdS6bmPQ69sNx1tuBoLNl$F56z`!8Nz`$z6z`&lvz`(JL zfq|<If_aWGFo3Zi0|SpA0|W0m2<GBoU;tt6dkhR9%xlKL0K#0K7#Kj9JB5LPJ0(8e z(>K6BC?wv+HP|`GGa$r2C_W{#I61#4MYpIRnIS&jFVx2eQxQWwk}8Or5RXtl?|2u- z5Jv_VhzaqYd6^}di8+~7sSNS)nYjh=SR`OV!5O6`Df#7jXe!~tFm*wtdCrMBIZ27h z*~MsTQKVq%Lkm(8OHu=hQj1gblF{9PCJEEyoRgYZghLCe985=0YH>k+UJAN{prSBU z5bHb=^HOqBi_l$<CJECMl$xGdT#{OZVKh`2U0rZVYGST?VlKK@Vdfy@NllMt28i^? z!@$5`#LB>s!_L64jDvxpj*Efe2oD2;9}fe=I$j0_4lV|Ud)y2RX1ojxpSTzpQn<kh zn1O?Vg@K!ahk=uUje(Vci-Dbik%0+|C^I|_VGnPll!YDuaGjVwMks~E7MzcqreJ~? zDGx^kL4=V~3z<0zHHV;j8&wX;^QaOqALB?Xh^R-`1_=%{QRF~Dm4XKisxTxZ@Fs6~ zFySpfkjfUll$;!Wf+eMaCA_3eX=7$MQu8-jn2~{jg8>5cQc{yj(^Y~YJO&0=1_p*6 z7zau_FbFv5Wu_#SB&rxeg&0H_7#J=xFfcrY3NSM;FgP$69Du4bg9<YUFfcGMGa}T1 z#2pwE4lpq=Fo-d5g18L)Xr5AM5Efu(U|_famStezV_;yI9w+3RnOu}#oS#;rpb@00 z;N$7%4V6csK-mxE7!YP)U;$YVb_6S!g=Q0jLvns@fnH|H^f*Sw|Nj{n5V}Byf*i@j z0QHiQ0|Nut*%>equqQz}m|-F&AT=PJa5bg~5s<hFh=5{{=?o0EP#%b4Vu)g3V4zLK z!-eK`f+@$y6ju2raHX^{fm6<21_lO*vl$q$rJNLyFOV=&$}xrsgHp~)7zav&QVz&m znxqI~;#d|O?l>xRl0(iCIpiRD?jeDY!;|Msp@9U>v!FZ&<AZ1i1_xN4GXe2I7?ihf zf*42`q>d&bOibu1GBDt86_FakrWVjbO##&Y17+NlHc-i?2KF%vyq-g?D$s0UK;sk4 z5vHctZB=GqU|?iG*ossMp;^H|axOGA#%?DlNSToAgx7Br+h}Hl-9{~N0gCV?%tl1j ziEcF-pK$o%uvvitRFEUv45^gS%qKlQ&9VDbm4ShQ6*+8?t9x`ysT!kfNVdXT5|rBr jYXYF<7Ig*&26iMH(Hbc<wHMUdg*7K3_QKmg81@1Hk71Kp literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c125c4b8..87b9dbedc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,8 @@ set(SRB2_CONFIG_HAVE_GME ON CACHE BOOL "Enable GME support.") set(SRB2_CONFIG_HAVE_OPENMPT ON CACHE BOOL "Enable OpenMPT 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.") set(SRB2_CONFIG_HAVE_THREADS ON CACHE BOOL @@ -66,6 +68,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_OPENMPT}) if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES}) set(OPENMPT_FOUND ON) diff --git a/src/Makefile b/src/Makefile index ce0e84987..36a1d89f5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -123,6 +123,9 @@ # SDL_PKGCONFIG= # SDL_CONFIG= - sdl-config command. # SDL_CFLAGS=, SDL_LDFLAGS= +# +# HAVE_DISCORDRPC=1 - Discord RPC integration + clean_targets=cleandep clean distclean info diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk index 46194390d..af5ce928f 100644 --- a/src/Makefile.d/features.mk +++ b/src/Makefile.d/features.mk @@ -58,6 +58,12 @@ ifdef HAVE_MINIUPNPC libs+=-lminiupnpc endif +ifdef HAVE_DISCORDRPC +libs+=-ldiscord-rpc +opts+=-DHAVE_DISCORDRPC -DUSE_STUN +sources+=discord.c stun.c +endif + # (Valgrind is a memory debugger.) ifdef VALGRIND VALGRIND_PKGCONFIG?=valgrind diff --git a/src/Makefile.d/win32.mk b/src/Makefile.d/win32.mk index 0c671b268..739e2220b 100644 --- a/src/Makefile.d/win32.mk +++ b/src/Makefile.d/win32.mk @@ -47,6 +47,17 @@ x86=x86_64 i686=x86_64 endif +ifdef HAVE_DISCORDRPC +ifdef MINGW64 +opts+=-I../libs/discord-rpc/win64-dynamic/include +libs+=-L../libs/discord-rpc/win64-dynamic/lib +else +opts+=-I../libs/discord-rpc/win32-dynamic/include +libs+=-L../libs/discord-rpc/win32-dynamic/lib +endif +libs+=-ldiscord-rpc +endif + mingw:=$(i686)-w64-mingw32 define _set = diff --git a/src/Sourcefile b/src/Sourcefile index 0dee91567..178e60d87 100755 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -96,4 +96,3 @@ lua_polyobjlib.c lua_blockmaplib.c lua_hudlib.c hashtable.c - diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 06232e625..3a324ebc9 100755 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -53,6 +53,10 @@ #include "f_finale.h" #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // // NETWORKING // @@ -3099,6 +3103,9 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) } else CL_RemovePlayer(pnum, kickreason); +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static void Command_ResendGamestate(void) @@ -3132,10 +3139,11 @@ static void Command_ResendGamestate(void) static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}}; consvar_t cv_netticbuffer = CVAR_INIT ("netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL); -consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); +static void Joinable_OnChange(void); +consvar_t cv_allownewplayer = CVAR_INIT ("allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, Joinable_OnChange); consvar_t cv_joinnextround = CVAR_INIT ("joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); /// \todo not done static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}}; -consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR, maxplayers_cons_t, NULL); +consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_SAVE|CV_NETVAR, maxplayers_cons_t, Joinable_OnChange); static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}}; consvar_t cv_joindelay = CVAR_INIT ("joindelay", "10", CV_SAVE|CV_NETVAR, joindelay_cons_t, NULL); static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}}; @@ -3145,6 +3153,10 @@ static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, { consvar_t cv_resynchattempts = CVAR_INIT ("resynchattempts", "10", CV_SAVE|CV_NETVAR, resynchattempts_cons_t, NULL); consvar_t cv_blamecfail = CVAR_INIT ("blamecfail", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL); +// Here for dedicated servers +static CV_PossibleValue_t discordinvites_cons_t[] = {{0, "Admins Only"}, {1, "Everyone"}, {0, NULL}}; +consvar_t cv_discordinvites = CVAR_INIT ("discordinvites", "Everyone", CV_SAVE|CV_CALL, discordinvites_cons_t, Joinable_OnChange); + // max file size to send to a player (in kilobytes) static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}}; consvar_t cv_maxsend = CVAR_INIT ("maxsend", "4096", CV_SAVE|CV_NETVAR, maxsend_cons_t, NULL); @@ -3156,6 +3168,24 @@ consvar_t cv_downloadspeed = CVAR_INIT ("downloadspeed", "16", CV_SAVE|CV_NETVAR static void Got_AddPlayer(UINT8 **p, INT32 playernum); +static void Joinable_OnChange(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); +} + // called one time at init void D_ClientServerInit(void) { @@ -3480,6 +3510,9 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (!rejoined) LUAh_PlayerJoin(newplayernum); +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static boolean SV_AddWaitingPlayers(const char *name, const char *name2) @@ -3950,6 +3983,14 @@ static void HandlePacketFromAwayNode(SINT8 node) memcpy(server_context, netbuffer->u.servercfg.server_context, 8); } +#ifdef HAVE_DISCORDRPC + discordInfo.maxPlayers = netbuffer->u.serverinfo.maxplayer; + /*discordInfo.joinsAllowed = netbuffer->u.servercfg.allownewplayer; + discordInfo.everyoneCanInvite = netbuffer->u.servercfg.discordinvites;*/ + discordInfo.joinsAllowed = true; + discordInfo.everyoneCanInvite = true; +#endif + nodeingame[(UINT8)servernode] = true; serverplayer = netbuffer->u.servercfg.serverplayer; doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum); diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 1a7d7e988..4e53a3aac 100755 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -171,6 +171,13 @@ typedef struct UINT8 modifiedgame; 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; typedef struct @@ -340,7 +347,7 @@ extern INT32 mapchangepending; // Points inside doomcom extern doomdata_t *netbuffer; - +extern consvar_t cv_stunserver; extern consvar_t cv_showjoinaddress; extern consvar_t cv_playbackspeed; @@ -408,6 +415,8 @@ extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_joinnextround, cv_maxpla extern consvar_t cv_resynchattempts, cv_blamecfail; extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed; +extern consvar_t cv_discordinvites; + // Used in d_net, the only dependence tic_t ExpandTics(INT32 low, INT32 node); void D_ClientServerInit(void); diff --git a/src/d_main.c b/src/d_main.c index 55de3e7c4..589c033ea 100755 --- a/src/d_main.c +++ b/src/d_main.c @@ -95,6 +95,10 @@ int VERSION; int SUBVERSION; +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // platform independant focus loss UINT8 window_notinfocus = false; @@ -783,6 +787,12 @@ void D_SRB2Loop(void) #endif LUA_Step(); +#ifdef HAVE_DISCORDRPC + if (! dedicated) + { + Discord_RunCallbacks(); + } +#endif } } @@ -1563,6 +1573,12 @@ void D_SRB2Main(void) if (!P_LoadLevel(false, false)) I_Quit(); // fail so reset game stuff } +#ifdef HAVE_DISCORDRPC + if (! dedicated) + { + DRPC_Init(); + } +#endif } const char *D_Home(void) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index b2c70375e..3b7da8763 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -54,6 +54,10 @@ #define CV_RESTRICT 0 #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // ------ // protos // ------ @@ -70,6 +74,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); @@ -672,6 +677,13 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_allowseenames); CV_RegisterVar(&cv_dummyconsvar); + +#ifdef USE_STUN + CV_RegisterVar(&cv_stunserver); +#endif + + CV_RegisterVar(&cv_discordinvites); + RegisterNetXCmd(XD_DISCORD, Got_DiscordInfo); } #include "i_net.h" @@ -1056,6 +1068,13 @@ void D_RegisterClientCommands(void) #ifdef 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_discordshowchar); +#endif } /** Checks if a name (as received from another player) is okay. @@ -1675,6 +1694,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) @@ -2267,6 +2291,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) if (demorecording) // Okay, level loaded, character spawned and skinned, G_BeginRecording(); // I AM NOW READY TO RECORD. demo_start = true; + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static void Command_Pause(void) @@ -3990,6 +4018,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. @@ -4824,3 +4856,32 @@ static void BaseNumLaps_OnChange(void) CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value); } } + + +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) + { + SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY); + } + 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);*/ + discordInfo.maxPlayers = READUINT8(*p); + discordInfo.joinsAllowed = (boolean)true; + discordInfo.everyoneCanInvite = (boolean)true; + + DRPC_UpdatePresence(); +#else + (*p) += 3; +#endif +} diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 773aa3a42..def02aa29 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -166,6 +166,7 @@ typedef enum XD_LUACMD, // 22 XD_LUAVAR, // 23 XD_LUAFILE, // 24 + XD_DISCORD, // 25 MAXNETXCMD } netxcmd_t; diff --git a/src/discord.c b/src/discord.c new file mode 100644 index 000000000..b04a97e69 --- /dev/null +++ b/src/discord.c @@ -0,0 +1,744 @@ +// 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 + +#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 "stun.h" +#include "i_tcp.h" // current_port + +#include "discord.h" +#include "doomdef.h" + +#ifdef HAVE_CURL +#include <curl/curl.h> +#endif + +// Feel free to provide your own, if you care enough to create another Discord app for this :P +#define DISCORD_APPID "875107153734680626" + +// length of IP strings +#define IP_SIZE 21 + +consvar_t cv_discordrp = CVAR_INIT ("discordrp", "On", CV_SAVE|CV_CALL, CV_OnOff, DRPC_UpdatePresence); +consvar_t cv_discordstreamer = CVAR_INIT ("discordstreamer", "Off", CV_SAVE, CV_OnOff, NULL); +consvar_t cv_discordasks = CVAR_INIT ("discordasks", "Yes", CV_SAVE|CV_CALL, CV_YesNo, DRPC_UpdatePresence); +consvar_t cv_discordshowchar = CVAR_INIT ("discordshowchar", "Yes", CV_SAVE|CV_CALL, CV_YesNo, DRPC_UpdatePresence); +struct discordInfo_s discordInfo; + +discordRequest_t *discordRequestList = NULL; + +static char self_ip[IP_SIZE+1]; + +#ifdef HAVE_CURL +#define DISCORD_CHARLIST_URL "http://srb2.mooo.com/SRB2RPC/customcharlist" +static void DRPC_GetCustomCharList(void *ptr); +static const char *customCharList[218]; +static INT32 extraCharCount = 0; +#endif +static boolean customCharSupported = false; + +/*-------------------------------------------------- + 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, 17, 18, 19, 20, 21}; + 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); +} + +#ifdef HAVE_CURL +typedef struct { + char *memory; + size_t size; +} curldata_t; + +static size_t WriteToArray(void *contents, size_t size, size_t nmemb, void *userdata) +{ + size_t realsize = size * nmemb; + curldata_t *mem = (curldata_t*)userdata; + + char *ptr = realloc(mem->memory, mem->size + realsize + 1); + + if (!ptr) + I_Error("Out of memory!\n"); + + mem->memory = ptr; + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +static void DRPC_GetCustomCharList(void* ptr) +{ + CURL *curl; + CURLcode cc; + curldata_t data; + char *stoken; + + (void)ptr; + + data.memory = malloc(1); + data.size = 0; + + // Download the list of latest supported custom characters + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_URL, DISCORD_CHARLIST_URL); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToArray); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + cc = curl_easy_perform(curl); + if (cc != CURLE_OK) + { + curl_easy_cleanup(curl); + CONS_Printf("Discord: Could not connect to custom character server list.\n"); + return; + } + + curl_easy_cleanup(curl); + } + + stoken = strtok(data.memory, "\n"); + while (stoken) + { + customCharList[extraCharCount] = strdup(stoken); + stoken = strtok(NULL, "\n"); + extraCharCount++; + } + + free(data.memory); + customCharSupported = true; +} +#endif + +/*-------------------------------------------------- + void DRPC_Init(void) + + See header file for description. +--------------------------------------------------*/ +void DRPC_Init(void) +{ + DiscordEventHandlers handlers; + +#ifdef HAVE_CURL + I_spawn_thread("get-custom-char-list", &DRPC_GetCustomCharList, NULL); +#endif + + 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(); +} + +/*-------------------------------------------------- + static void DRPC_GotServerIP(UINT32 address) + + Callback triggered by successful STUN response. + + Input Arguments:- + address - IPv4 address of this machine, in network byte order. + + Return:- + None +--------------------------------------------------*/ +static void DRPC_GotServerIP(UINT32 address) +{ + const unsigned char * p = (const unsigned char *)&address; + sprintf(self_ip, "%u.%u.%u.%u:%u", p[0], p[1], p[2], p[3], current_port); +} + +/*-------------------------------------------------- + static const char *DRPC_GetServerIP(void) + + Retrieves the IP address of the server that you're + connected to. Will attempt to use STUN for getting your + own IP address. +--------------------------------------------------*/ +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; + } + } + + if (self_ip[0]) + { + return self_ip; + } + else + { + // There happens to be a good way to get it after all! :D + STUN_bind(DRPC_GotServerIP); + return NULL; + } +} + +/*-------------------------------------------------- + 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 (dedicated) + { + return; + } + + 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) + { + if (DRPC_InvitesAreAllowed() == true) + { + const char *join; + + // Grab the host's IP for joining. + if ((join = DRPC_GetServerIP()) != NULL) + { + discordPresence.joinSecret = DRPC_XORIPString(join); + joinSecretSet = true; + } + } + + // unfortunally this only works when you are the server + /*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.state = "Multiplayer"; + + 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 + } + 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()) + { + UINT8 emeraldCount = 0; + discordPresence.state = "Single-Player"; + + if (emeralds == 0) + discordPresence.details = "No Chaos Emeralds"; + else + { + for (INT32 i = 0; i < 7; i++) // thanks Monster Iestyn for this math + if (emeralds & (1<<i)) + emeraldCount += 1; + + if (emeraldCount > 1 && emeraldCount < 7 && emeraldCount != 3) + discordPresence.details = va("Has %d Chaos Emeralds", emeraldCount); + else if (emeraldCount == 1) + discordPresence.details = "Has 1 Chaos Emerald"; + else if (emeraldCount == 3) + discordPresence.details = "Where's that DAMN fourth chaos emerald (Has 3 Emeralds)"; + else + discordPresence.details = "Has All The 7 Chaos Emeralds"; + } + + + } + else if (demoplayback && !titledemo) + discordPresence.state = "Watching Replay"; + else + discordPresence.state = "Menu"; + } + + // Gametype info + if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && Playing()) + { + if (modeattacking) + discordPresence.details = "Time Attack"; + else if (netgame) + { + snprintf(detailstr, 48, "%s", + gametype_cons_t[gametype].strvalue + ); + discordPresence.details = detailstr; + } + } + + if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) // Map info + && !(demoplayback && titledemo)) + { + if ((gamemap >= 1 && gamemap <= 73) // Supported Co-op maps + || (gamemap >= 280 && gamemap <= 288) // Supported CTF maps + || (gamemap >= 532 && gamemap <= 543)) // Supported Match 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 / TICRATE); + + discordPresence.startTimestamp = mapTimeStart; + + if (timelimitintics > 0) + { + const time_t mapTimeEnd = mapTimeStart + ((timelimitintics + 1) / TICRATE); + discordPresence.endTimestamp = mapTimeEnd; + } + } + } + else + { + discordPresence.largeImageKey = "misctitle"; + discordPresence.largeImageText = "Title Screen"; + } + + // Character info + if (cv_discordshowchar.value && Playing() && playeringame[consoleplayer] && !players[consoleplayer].spectator) + { + // Supported skin names + static const char *supportedSkins[] = { + // base game + "sonic", + "tails", + "knuckles", + "metalsonic", + "fang", + "amy", + NULL + }; + + boolean customChar = true; + boolean sonicAndTails = false; + UINT8 checkSkin = 0; + + if (!netgame) + { + if (players[1].bot && !strcmp(skins[players[consoleplayer].skin].name, "sonic")) + { + snprintf(charimg, 21, "charsonictails"); + snprintf(charname, 28, "Characters: Sonic & Tails"); + discordPresence.smallImageKey = charimg; + sonicAndTails = true; + customChar = false; + } + } + + if (!sonicAndTails) + { + // 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) + { + INT32 i; + boolean notfound = true; + + // Custom Character image + if (customCharSupported) + for (i = 0; i < extraCharCount; i++) + { + if (!strcmp(skins[players[consoleplayer].skin].name, customCharList[i])) + { + snprintf(charimg, 21, "char%s", customCharList[i]); + discordPresence.smallImageKey = charimg; + notfound = false; + break; + } + } + + if (notfound) // 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 diff --git a/src/discord.h b/src/discord.h new file mode 100644 index 000000000..2024dd69f --- /dev/null +++ b/src/discord.h @@ -0,0 +1,81 @@ +// 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_discordshowchar; + +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_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__ \ No newline at end of file diff --git a/src/doomdef.h b/src/doomdef.h index 9dc44d3bb..46eac9030 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -124,6 +124,12 @@ extern char logfilename[1024]; /* A mod name to further distinguish versions. */ #define SRB2APPLICATION "SRB2" +// uncapped ver +#define UNCAPPEDVERSION "1.0" + +// Netplus version +#define NETPLUSVERSION "Bleeding Edge Build" + //#define DEVELOP // Disable this for release builds to remove excessive cheat commands and enable MD5 checking and stuff, all in one go. :3 #ifdef DEVELOP #define VERSIONSTRING "Development EXE" @@ -134,6 +140,11 @@ extern char logfilename[1024]; #ifdef BETAVERSION #define VERSIONSTRING "v"SRB2VERSION" "BETAVERSION #define VERSIONSTRING_RC SRB2VERSION " " BETAVERSION "\0" +#elif defined (UNCAPPEDVERSION) +// #define VERSIONSTRING "v"SRB2VERSION" (Uncapped v"UNCAPPEDVERSION" NetPLUS v"NETPLUSVERSION")" +// #define VERSIONSTRING_RC SRB2VERSION " (UCNetPLUS v"NETPLUSVERSION" "NETPLUSCOMMIT")\0" +#define VERSIONSTRING "v"SRB2VERSION" (NetPLUS "NETPLUSVERSION")" +#define VERSIONSTRING_RC SRB2VERSION " (NetPLUS "NETPLUSVERSION")\0" #else #define VERSIONSTRING "v"SRB2VERSION #define VERSIONSTRING_RC SRB2VERSION "\0" diff --git a/src/g_game.c b/src/g_game.c index 33a4eb0e5..10d125fcd 100755 --- a/src/g_game.c +++ b/src/g_game.c @@ -48,6 +48,10 @@ #include "lua_hud.h" +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; @@ -5147,6 +5151,9 @@ INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) void G_SetGamestate(gamestate_t newstate) { gamestate = newstate; +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } /* These functions handle the exitgame flag. Before, when the user diff --git a/src/i_tcp.c b/src/i_tcp.c index 679553039..7d512dd47 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -142,6 +142,7 @@ #include "i_system.h" #include "i_net.h" +#include "stun.h" #include "d_net.h" #include "d_netfil.h" #include "i_tcp.h" @@ -568,7 +569,13 @@ static boolean SOCK_Get(void) c = recvfrom(mysockets[n], (char *)&doomcom->data, MAXPACKETLENGTH, 0, (void *)&fromaddress, &fromlen); if (c != ERRSOCKET) - { + { +#ifdef USE_STUN + if (STUN_got_response(doomcom->data, c)) + { + return false; + } +#endif // find remote node number for (j = 1; j <= MAXNETNODES; j++) //include LAN { diff --git a/src/m_menu.c b/src/m_menu.c index c63bb69c3..670a4ba09 100755 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -79,6 +79,11 @@ #define FIXUPO0 #endif +#ifdef HAVE_DISCORDRPC +//#include "discord_rpc.h" +#include "discord.h" +#endif + #define SKULLXOFF -32 #define LINEHEIGHT 16 #define STRINGHEIGHT 8 @@ -199,6 +204,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; // Level Select @@ -336,6 +347,9 @@ menu_t OP_SoundAdvancedDef; //Misc menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef; menu_t OP_ServerOptionsDef; +#ifdef HAVE_DISCORDRPC +menu_t OP_DiscordOptionsDef; +#endif menu_t OP_MonitorToggleDef; static void M_ScreenshotOptions(INT32 choice); static void M_SetupScreenshotMenu(void); @@ -559,6 +573,10 @@ static menuitem_t MPauseMenu[] = {IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16}, {IT_STRING | IT_CALL, NULL, "Switch Gametype/Level...", M_MapChange, 24}, + #ifdef HAVE_DISCORDRPC + {IT_STRING | IT_SUBMENU, NULL, "Ask To Join Requests...", &MISC_DiscordRequestsDef, 24}, + #endif + {IT_STRING | IT_CALL, NULL, "Continue", M_SelectableClearMenus,40}, {IT_STRING | IT_CALL, NULL, "Player 1 Setup", M_SetupMultiPlayer, 48}, // splitscreen {IT_STRING | IT_CALL, NULL, "Player 2 Setup", M_SetupMultiPlayer2, 56}, // splitscreen @@ -578,6 +596,9 @@ typedef enum mpause_addons = 0, mpause_scramble, mpause_switchmap, +#ifdef HAVE_DISCORDRPC + mpause_discordrequests, +#endif mpause_continue, mpause_psetupsplit, @@ -624,6 +645,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 // ----------------- @@ -1539,8 +1567,13 @@ static menuitem_t OP_DataOptionsMenu[] = { {IT_STRING | IT_CALL, NULL, "Add-on Options...", M_AddonsOptions, 10}, {IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 20}, +#ifdef HAVE_DISCORDRPC + {IT_STRING | IT_SUBMENU, NULL, "Discord Options...", &OP_DiscordOptionsDef, 40}, - {IT_STRING | IT_SUBMENU, NULL, "\x85" "Erase Data...", &OP_EraseDataDef, 40}, + {IT_STRING | IT_SUBMENU, NULL, "\x85" "Erase Data...", &OP_EraseDataDef, 60}, +#else + {IT_STRING | IT_SUBMENU, NULL, "\x85" "Erase Data...", &OP_EraseDataDef, 50}, +#endif }; static menuitem_t OP_ScreenshotOptionsMenu[] = @@ -1611,6 +1644,20 @@ 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}, + {IT_STRING | IT_CVAR, NULL, "Show Character on Status", &cv_discordshowchar, 80}, +}; +#endif + static menuitem_t OP_ServerOptionsMenu[] = { {IT_HEADER, NULL, "General", NULL, 0}, @@ -1761,6 +1808,20 @@ menu_t MISC_ChangeLevelDef = menu_t MISC_HelpDef = IMAGEDEF(MISC_HelpMenu); +#ifdef HAVE_DISCORDRPC +menu_t MISC_DiscordRequestsDef = { + MN_DISCORD_RQ, + NULL, + sizeof (MISC_DiscordRequestsMenu)/sizeof (menuitem_t), + &MPauseDef, + MISC_DiscordRequestsMenu, + M_DrawDiscordRequests, + 0, 0, + 0, + NULL +}; +#endif + static INT32 highlightflags, recommendedflags, warningflags; @@ -2302,6 +2363,10 @@ menu_t OP_EraseDataDef = DEFAULTMENUSTYLE( MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_OP_ERASEDATA), "M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30); +#ifdef HAVE_DISCORDRPC +menu_t OP_DiscordOptionsDef = DEFAULTMENUSTYLE(MTREE3(MN_OP_MAIN, MN_OP_DATA, MN_DISCORD_OPT), NULL, OP_DiscordOptionsMenu, &OP_DataOptionsDef, 30, 30); +#endif + //Netplus options menu menu_t OP_NetPlusDef = DEFAULTSCROLLMENUSTYLE( MTREE2(MN_OP_MAIN, MN_OP_NETPLUS), @@ -3780,6 +3845,11 @@ void M_StartControlPanel(void) MPauseMenu[mpause_switchteam].status = IT_DISABLED; MPauseMenu[mpause_psetup].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; + if ((server || IsPlayerAdmin(consoleplayer))) { MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL; @@ -3806,6 +3876,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; } @@ -5059,6 +5142,25 @@ static void M_DrawPauseMenu(void) } } +#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(); } @@ -5633,7 +5735,7 @@ static void M_HandleLevelPlatter(INT32 choice) { if (!lsoffs[0]) // prevent sound spam { - lsoffs[0] = -8; + lsoffs[0] = -8 * FRACUNIT; S_StartSound(NULL,sfx_s3kb7); } return; @@ -5642,7 +5744,7 @@ static void M_HandleLevelPlatter(INT32 choice) } lsrow++; - lsoffs[0] = lsvseperation(lsrow); + lsoffs[0] = lsvseperation(lsrow) * FRACUNIT; if (levelselect.rows[lsrow].header[0]) lshli = lsrow; @@ -5661,7 +5763,7 @@ static void M_HandleLevelPlatter(INT32 choice) { if (!lsoffs[0]) // prevent sound spam { - lsoffs[0] = 8; + lsoffs[0] = 8 * FRACUNIT; S_StartSound(NULL,sfx_s3kb7); } return; @@ -5670,7 +5772,7 @@ static void M_HandleLevelPlatter(INT32 choice) } lsrow--; - lsoffs[0] = -lsvseperation(iter); + lsoffs[0] = -lsvseperation(iter) * FRACUNIT; if (levelselect.rows[lsrow].header[0]) lshli = lsrow; @@ -5711,7 +5813,7 @@ static void M_HandleLevelPlatter(INT32 choice) } else if (!lsoffs[0]) // prevent sound spam { - lsoffs[0] = -8; + lsoffs[0] = -8 * FRACUNIT; S_StartSound(NULL,sfx_s3kb2); } break; @@ -5737,14 +5839,14 @@ static void M_HandleLevelPlatter(INT32 choice) { lscol++; - lsoffs[1] = (lswide(lsrow) ? 8 : -lshseperation); + lsoffs[1] = (lswide(lsrow) ? 8 : -lshseperation) * FRACUNIT; S_StartSound(NULL,sfx_s3kb7); ifselectvalnextmap(lscol) else ifselectvalnextmap(0) } else if (!lsoffs[1]) // prevent sound spam { - lsoffs[1] = 8; + lsoffs[1] = 8 * FRACUNIT; S_StartSound(NULL,sfx_s3kb7); } break; @@ -5769,14 +5871,14 @@ static void M_HandleLevelPlatter(INT32 choice) { lscol--; - lsoffs[1] = (lswide(lsrow) ? -8 : lshseperation); + lsoffs[1] = (lswide(lsrow) ? -8 : lshseperation) * FRACUNIT; S_StartSound(NULL,sfx_s3kb7); ifselectvalnextmap(lscol) else ifselectvalnextmap(0) } else if (!lsoffs[1]) // prevent sound spam { - lsoffs[1] = -8; + lsoffs[1] = -8 * FRACUNIT; S_StartSound(NULL,sfx_s3kb7); } break; @@ -5939,7 +6041,7 @@ static void M_DrawRecordAttackForeground(void) for (i = -12; i < (BASEVIDHEIGHT/height) + 12; i++) { - INT32 y = ((i*height) - (height - ((recatkdrawtimer*2)%height))); + INT32 y = ((i*height) - (height - ((FixedInt(recatkdrawtimer*2))%height))); // don't draw above the screen { INT32 sy = FixedMul(y, dupz<<FRACBITS) >> FRACBITS; @@ -5956,7 +6058,7 @@ static void M_DrawRecordAttackForeground(void) } // draw clock - fa = (FixedAngle(((recatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK; + fa = (FixedAngle(((FixedInt(recatkdrawtimer * 4)) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK; V_DrawSciencePatch(160<<FRACBITS, (80<<FRACBITS) + (4*FINESINE(fa)), 0, clock, FRACUNIT); // Increment timer. @@ -7134,7 +7236,11 @@ static void M_Options(INT32 choice) OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); // if the player is playing _at all_, disable the erase data options +#ifdef HAVE_DISCORDRPC + OP_DataOptionsMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); +#else OP_DataOptionsMenu[2].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); +#endif OP_MainDef.prevMenu = currentMenu; M_SetupNextMenu(&OP_MainDef); @@ -7169,6 +7275,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 // ====== @@ -13633,3 +13753,160 @@ static void M_QuitSRB2(INT32 choice) (void)choice; M_StartMessage(quitmsg[M_RandomKey(NUM_QUITMESSAGES)], M_QuitResponse, MM_YESNO); } +#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_CACHE); + hand = W_CachePatchName("K_LAPH02", PU_CACHE); + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_CACHE); + 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_CACHE); + } + + 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 \ No newline at end of file diff --git a/src/m_menu.h b/src/m_menu.h index 666e57556..05775cc53 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -138,6 +138,8 @@ typedef enum // MN_HELP, MN_SPECIAL, + MN_DISCORD_RQ, + MN_DISCORD_OPT, NUMMENUTYPES, } menutype_t; // up to 63; MN_SPECIAL = 53 #define MTREE2(a,b) (a | (b<<MENUBITS)) diff --git a/src/m_swap.h b/src/m_swap.h index 6aa347d97..9ed8fc15f 100644 --- a/src/m_swap.h +++ b/src/m_swap.h @@ -18,28 +18,32 @@ // WAD files are stored little endian. #include "endian.h" +#define SWAP_SHORT(x) ((INT16)(\ +(((UINT16)(x) & (UINT16)0x00ffU) << 8) \ +| \ +(((UINT16)(x) & (UINT16)0xff00U) >> 8))) \ + +#define SWAP_LONG(x) ((INT32)(\ +(((UINT32)(x) & (UINT32)0x000000ffUL) << 24) \ +| \ +(((UINT32)(x) & (UINT32)0x0000ff00UL) << 8) \ +| \ +(((UINT32)(x) & (UINT32)0x00ff0000UL) >> 8) \ +| \ +(((UINT32)(x) & (UINT32)0xff000000UL) >> 24))) + // Little to big endian #ifdef SRB2_BIG_ENDIAN - - #define SHORT(x) ((INT16)(\ - (((UINT16)(x) & (UINT16)0x00ffU) << 8) \ - | \ - (((UINT16)(x) & (UINT16)0xff00U) >> 8))) \ - - #define LONG(x) ((INT32)(\ - (((UINT32)(x) & (UINT32)0x000000ffUL) << 24) \ - | \ - (((UINT32)(x) & (UINT32)0x0000ff00UL) << 8) \ - | \ - (((UINT32)(x) & (UINT32)0x00ff0000UL) >> 8) \ - | \ - (((UINT32)(x) & (UINT32)0xff000000UL) >> 24))) - +#define SHORT SWAP_SHORT +#define LONG SWAP_LONG +#define MSBF_SHORT(x) ((INT16)(x)) +#define MSBF_LONG(x) ((INT32)(x)) #else - #define SHORT(x) ((INT16)(x)) - #define LONG(x) ((INT32)(x)) +#define SHORT(x) ((INT16)(x)) +#define LONG(x) ((INT32)(x)) +#define MSBF_SHORT SWAP_SHORT +#define MSBF_LONG SWAP_LONG #endif - // Big to little endian #ifdef SRB2_LITTLE_ENDIAN #define BIGENDIAN_LONG(x) ((INT32)(((x)>>24)&0xff)|(((x)<<8)&0xff0000)|(((x)>>8)&0xff00)|(((x)<<24)&0xff000000)) diff --git a/src/mserv.c b/src/mserv.c index f64c7bea9..c38538034 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -23,6 +23,10 @@ #include "m_menu.h" #include "z_zone.h" +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + #ifdef MASTERSERVER static int MSId; @@ -64,7 +68,7 @@ static CV_PossibleValue_t masterserver_update_rate_cons_t[] = { consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange); consvar_t cv_servername = CVAR_INIT ("servername", "SRB2 server", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, NULL, Update_parameters); -consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters); +consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "5", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters); INT16 ms_RoomId = -1; @@ -266,6 +270,10 @@ Finish_update (void) if (! done) Finish_update(); +#ifdef HAVE_DISCORDRPC + else + DRPC_UpdatePresence(); +#endif } static void diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index 4f19d93df..d5db7cd7c 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -99,6 +99,7 @@ if(${SDL2_FOUND}) ${ZLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${CURL_LIBRARIES} + ${DISCORDRPC_LIBRARIES} ) set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") else() @@ -112,6 +113,7 @@ if(${SDL2_FOUND}) ${ZLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${CURL_LIBRARIES} + ${DISCORDRPC_LIBRARIES} ) if(${CMAKE_SYSTEM} MATCHES Linux) @@ -157,6 +159,7 @@ if(${SDL2_FOUND}) ${ZLIB_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} + ${DISCORDRPC_INCLUDE_DIRS} ) if((${SRB2_HAVE_MIXER}) OR (${SRB2_HAVE_MIXERX})) @@ -271,6 +274,11 @@ if(${SDL2_FOUND}) getwinlib(libstdc++-6 "libstdc++-6.dll") endif() + if(${SRB2_CONFIG_HAVE_DISCORDRPC}) + getwinlib(discord-rpc "discord-rpc.dll") + endif() + + install(PROGRAMS ${win_extra_dll_list} DESTINATION . diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 4e6b30d2a..972a171a2 100755 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -2168,8 +2168,6 @@ tic_t I_GetTime(void) It also messes with SRB2netplus's timer fudge, meaning that for a truly accurate timerfudge it needs to know which timer the server is using... Fudge the timer to sync better with online games. Uses multiply-first approach (more accurate)*/ - //We need more testing if it surely does make the game more jittery to play - JF049 - // fudge the timer for better netgame sync if (cv_timefudge.value != lastTimeFudge) { @@ -2182,9 +2180,9 @@ tic_t I_GetTime(void) } elapsed = (double)(elapsed + cv_timefudge.value / 100); - // 100? probably it's just to get a float number from 0 to 1 by dividing timefudge/100 + // 100 is just to get a float number from 0 to 1 by dividing timefudge/100 // this probably allows to move the time in slight steps - // knowing that this is a FP value, this makes sense. + // knowing that "elapsed" is a FP value, this makes sense. // jitters happen when we are not "even" with timers within 0..1 (and also depending on latency) lastTimeFudge = cv_timefudge.value; diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 5f18720f8..fed719fa8 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -83,6 +83,10 @@ #include "ogl_sdl.h" #endif +#ifdef HAVE_DISCORDRPC +#include "../discord.h" +#endif + // maximum number of windowed modes (see windowedModes[][]) #define MAXWINMODES (18) @@ -1213,6 +1217,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; diff --git a/src/sounds.c b/src/sounds.c index 4c5b11ee9..c911184d7 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -822,6 +822,12 @@ sfxinfo_t S_sfx[NUMSFX] = {"kc6d", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"kc6e", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // discord rpc + {"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"}, + // skin sounds free slots to add sounds at run time (Boris HACK!!!) // initialized to NULL }; diff --git a/src/sounds.h b/src/sounds.h index 2dd37953c..0c187e000 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -871,6 +871,12 @@ typedef enum sfx_kc6d, sfx_kc6e, + // discord rpc + sfx_join, + sfx_leave, + sfx_requst, + sfx_syfail, + // free slots for S_AddSoundFx() at run-time -------------------- sfx_freeslot0, // diff --git a/src/st_stuff.c b/src/st_stuff.c index a0457ba53..1f537b7d4 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -130,6 +130,11 @@ static patch_t *fnshico; static boolean facefreed[MAXPLAYERS]; +#ifdef HAVE_DISCORDRPC +// Discord Rich Presence +static patch_t *envelope; +#endif + hudinfo_t hudinfo[NUMHUDITEMS] = { { 16, 176, V_SNAPTOLEFT|V_SNAPTOBOTTOM}, // HUD_LIVES @@ -340,6 +345,11 @@ void ST_LoadGraphics(void) for (i = 0; i < 7; ++i) ngradeletters[i] = W_CachePatchName(va("GRADE%d", i), 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 @@ -2747,6 +2757,22 @@ static void ST_overlayDrawer(void) ST_drawDebugInfo(); } +#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) { if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo) diff --git a/src/st_stuff.h b/src/st_stuff.h index b1ea2942d..4f705bb51 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -32,6 +32,11 @@ void ST_Drawer(void); // Called when the console player is spawned on each level. void ST_Start(void); +#ifdef HAVE_DISCORDRPC +// Called when you have Discord asks +void ST_AskToJoinEnvelope(void); +#endif + // Called by startup code. void ST_Init(void); diff --git a/src/stun.c b/src/stun.c new file mode 100644 index 000000000..fe485ad82 --- /dev/null +++ b/src/stun.c @@ -0,0 +1,233 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// 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 stun.c +/// \brief RFC 5389 client implementation to fetch external IP address. + +/* https://tools.ietf.org/html/rfc5389 */ + +#if defined (__linux__) +#include <sys/random.h> +#elif defined (_WIN32) +#define _CRT_RAND_S +#elif defined (__APPLE__) +#include <CommonCrypto/CommonRandom.h> +#else +#error "Need CSPRNG." +#endif + +#include "doomdef.h" +#include "d_clisrv.h" +#include "command.h" +#include "i_net.h" +#include "stun.h" + +/* https://gist.github.com/zziuni/3741933 */ +/* I can only trust google to keep their shit up :y */ +consvar_t cv_stunserver = CVAR_INIT ("stunserver", "stun.l.google.com:19302", CV_SAVE, NULL, NULL); + +static stun_callback_t stun_callback; + +/* 18.4 STUN UDP and TCP Port Numbers */ + +#define STUN_PORT "3478" + +/* 6. STUN Message Structure */ + +#define BIND_REQUEST 0x0001 +#define BIND_RESPONSE 0x0101 + +static const UINT32 MAGIC_COOKIE = MSBF_LONG (0x2112A442); + +static char transaction_id[12]; + +/* 18.2 STUN Attribute Registry */ + +#define XOR_MAPPED_ADDRESS 0x0020 + +/* 15.1 MAPPED-ADDRESS */ + +#define STUN_IPV4 0x01 + +static SINT8 +STUN_node (void) +{ + SINT8 node; + + char * const colon = strchr(cv_stunserver.zstring, ':'); + + const char * const host = cv_stunserver.zstring; + const char * const port = &colon[1]; + + I_Assert(I_NetMakeNodewPort != NULL); + + if (colon != NULL) + { + *colon = '\0'; + + node = I_NetMakeNodewPort(host, port); + + *colon = ':'; + } + else + { + node = I_NetMakeNodewPort(host, STUN_PORT); + } + + return node; +} + +static void +csprng +( + void * const buffer, + const size_t size +){ +#if defined (_WIN32) + size_t o; + + for (o = 0; o < size; o += sizeof (unsigned int)) + { + rand_s((unsigned int *)&((char *)buffer)[o]); + } +#elif defined (__linux__) + getrandom(buffer, size, 0U); +#elif defined (__APPLE__) + CCRandomGenerateBytes(buffer, size); +#elif defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) + arc4random_buf(buffer, size); +#endif +} + +void +STUN_bind (stun_callback_t callback) +{ + /* 6. STUN Message Structure */ + + const UINT16 type = MSBF_SHORT (BIND_REQUEST); + + const SINT8 node = STUN_node(); + + doomcom->remotenode = node; + doomcom->datalength = 20; + + csprng(transaction_id, 12U); + + memcpy(&doomcom->data[0], &type, 2U); + memset(&doomcom->data[2], 0, 2U); + memcpy(&doomcom->data[4], &MAGIC_COOKIE, 4U); + memcpy(&doomcom->data[8], transaction_id, 12U); + + stun_callback = callback; + + I_NetSend(); + Net_CloseConnection(node);/* will handle response at I_NetGet */ +} + +static size_t +STUN_xor_mapped_address (const char * const value) +{ + const UINT32 xaddr = *(const UINT32 *)&value[4]; + const UINT32 addr = xaddr ^ MAGIC_COOKIE; + + (*stun_callback)(addr); + + return 0U; +} + +static size_t +align4 (size_t n) +{ + return n + n % 4U; +} + +static size_t +STUN_parse_attribute (const char * const attribute) +{ + /* 15. STUN Attributes */ + const UINT16 type = MSBF_SHORT (*(const UINT16 *)&attribute[0]); + const UINT16 length = MSBF_SHORT (*(const UINT16 *)&attribute[2]); + + /* 15.2 XOR-MAPPED-ADDRESS */ + if ( + type == XOR_MAPPED_ADDRESS && + length == 8U && + (unsigned char)attribute[5] == STUN_IPV4 + ){ + return STUN_xor_mapped_address(&attribute[4]); + } + + return align4(4U + length); +} + +boolean +STUN_got_response +( + const char * const buffer, + const size_t size +){ + const char * const end = &buffer[size]; + + const char * p = &buffer[20]; + + UINT16 type; + UINT16 length; + + /* + Check for STUN response. + + Header is 20 bytes. + XOR-MAPPED-ADDRESS attribute is required. + Each attribute has a 2 byte header. + The XOR-MAPPED-ADDRESS attribute also has a 8 byte value. + This totals 10 bytes for the attribute. + */ + + if (size < 30U || stun_callback == NULL) + { + return false; + } + + /* 6. STUN Message Structure */ + + if ( + *(const UINT32 *)&buffer[4] == MAGIC_COOKIE && + memcmp(&buffer[8], transaction_id, 12U) == 0 + ){ + type = MSBF_SHORT (*(const UINT16 *)&buffer[0]); + length = MSBF_SHORT (*(const UINT16 *)&buffer[2]); + + if ( + (type >> 14) == 0U && + (length & 0x02) == 0U && + (20U + length) <= size + ){ + if (type == BIND_RESPONSE) + { + do + { + length = STUN_parse_attribute(p); + + if (length == 0U) + { + break; + } + + p += length; + } + while (p < end) ; + } + + stun_callback = NULL; + + return true; + } + } + + return false; +} diff --git a/src/stun.h b/src/stun.h new file mode 100644 index 000000000..de23aeb42 --- /dev/null +++ b/src/stun.h @@ -0,0 +1,20 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// 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 stun.h +/// \brief RFC 5389 client implementation to fetch external IP address. + +#ifndef KART_STUN_H +#define KART_STUN_H + +typedef void (*stun_callback_t)(UINT32 address); + +void STUN_bind (stun_callback_t); +boolean STUN_got_response (const char * const buffer, const size_t size); + +#endif/*KART_STUN_H*/ diff --git a/src/v_video.c b/src/v_video.c index 9cbf6d792..95b448ac0 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -83,6 +83,10 @@ static CV_PossibleValue_t constextsize_cons_t[] = { static void CV_constextsize_OnChange(void); consvar_t cv_constextsize = CVAR_INIT ("con_textsize", "Medium", CV_SAVE|CV_CALL, constextsize_cons_t, CV_constextsize_OnChange); +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // local copy of the palette for V_GetColor() RGBA_t *pLocalPalette = NULL; RGBA_t *pMasterPalette = NULL; -- GitLab