diff --git a/cmake/Modules/FindDiscordRPC.cmake b/cmake/Modules/FindDiscordRPC.cmake new file mode 100644 index 000000000..e71762767 --- /dev/null +++ b/cmake/Modules/FindDiscordRPC.cmake @@ -0,0 +1,23 @@ +include(LibFindMacros) + +libfind_pkg_check_modules(DISCORDRPC_PKGCONF DISCORDRPC) + +find_path(DISCORDRPC_INCLUDE_DIR + NAMES discord_rpc.h + PATHS + ${DISCORDRPC_PKGCONF_INCLUDE_DIRS} + "/usr/include" + "/usr/local/include" +) + +find_library(DISCORDRPC_LIBRARY + NAMES discord-rpc + PATHS + ${DISCORDRPC_PKGCONF_LIBRARY_DIRS} + "/usr/lib" + "/usr/local/lib" +) + +set(DISCORDRPC_PROCESS_INCLUDES DISCORDRPC_INCLUDE_DIR) +set(DISCORDRPC_PROCESS_LIBS DISCORDRPC_LIBRARY) +libfind_process(DISCORDRPC) diff --git a/libs/DLL-README.txt b/libs/DLL-README.txt index bbbecbd0e..51963baca 100644 --- a/libs/DLL-README.txt +++ b/libs/DLL-README.txt @@ -1,6 +1,6 @@ # SRB2Kart - Which DLLs do I need to bundle? -Updated 12/6/2019 (v2.2) +Updated 8/23/2020 (v1.3) Here are the required DLLs, per build. For each architecture, copy all the binaries from these folders: @@ -15,6 +15,7 @@ and don't forget to build r_opengl.dll for srb2dd. * libs\dll-binaries\i686\exchndl.dll * libs\dll-binaries\i686\libgme.dll +* libs\dll-binaries\i686\discord-rpc.dll * libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll) * libs\SDL2\i686-w64-mingw32\bin\SDL2.dll * libs\SDL2mixerX\i686-w64-mingw32\bin\*.dll (get everything) @@ -24,27 +25,8 @@ and don't forget to build r_opengl.dll for srb2dd. * libs\dll-binaries\x86_64\exchndl.dll * libs\dll-binaries\x86_64\libgme.dll +* libs\dll-binaries\x86_64\discord-rpc.dll * libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll) * libs\SDL2\x86_64-w64-mingw32\bin\SDL2.dll -<<<<<<< HEAD -* libs\SDL2_mixer\x86_64-w64-mingw32\bin\*.dll (get everything) - -## srb2kartdd, 32-bit - -* libs\dll-binaries\i686\exchndl.dll -* libs\dll-binaries\i686\fmodex.dll -* libs\dll-binaries\i686\libgme.dll -* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll) -* r_opengl.dll (build this from make) - -## srb2kartdd, 64-bit - -* libs\dll-binaries\x86_64\exchndl.dll -* libs\dll-binaries\x86_64\fmodex.dll -* libs\dll-binaries\x86_64\libgme.dll -* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll) -* r_opengl.dll (build this from make) -======= * libs\SDL2mixerX\x86_64-w64-mingw32\bin\*.dll (get everything) * libs\libopenmpt\x86_64\bin\mingw\libopenmpt.dll ->>>>>>> srb2/next diff --git a/libs/discord-rpc.props b/libs/discord-rpc.props new file mode 100644 index 000000000..83c7a03f3 --- /dev/null +++ b/libs/discord-rpc.props @@ -0,0 +1,16 @@ + + + + + $(SolutionDir)libs\discord-rpc\win32-dynamic\lib;$(LibraryPath) + $(SolutionDir)libs\discord-rpc\win32-dynamic\lib;$(IncludePath) + $(SolutionDir)libs\discord-rpc\win64-dynamic\lib;$(LibraryPath) + $(SolutionDir)libs\discord-rpc\win64-dynamic\lib;$(IncludePath) + + + + discord-rpc.dll.a;%(AdditionalDependencies) + + + + \ No newline at end of file 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 + +// 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 000000000..d8b6689f3 Binary files /dev/null and b/libs/discord-rpc/win32-dynamic/lib/discord-rpc.lib differ 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 + +// 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 000000000..fcd009d82 Binary files /dev/null and b/libs/discord-rpc/win64-dynamic/lib/discord-rpc.lib differ diff --git a/libs/dll-binaries/i686/discord-rpc.dll b/libs/dll-binaries/i686/discord-rpc.dll new file mode 100644 index 000000000..88c7d0cef Binary files /dev/null and b/libs/dll-binaries/i686/discord-rpc.dll differ diff --git a/libs/dll-binaries/x86_64/discord-rpc.dll b/libs/dll-binaries/x86_64/discord-rpc.dll new file mode 100644 index 000000000..8493c5490 Binary files /dev/null and b/libs/dll-binaries/x86_64/discord-rpc.dll differ diff --git a/libs/drmingw/include/exchndl.h b/libs/drmingw/include/exchndl.h new file mode 100644 index 000000000..37dafb88e --- /dev/null +++ b/libs/drmingw/include/exchndl.h @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2015 Jose Fonseca + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#pragma once + + +#include + + +// Set the unhandled exception handler. +// Must be called when exchndll.dll is statically loaded (as opposed to loaded +// dynamically via LoadLibrary) +EXTERN_C VOID APIENTRY +ExcHndlInit(void); + + +// Override the report file name. +// +// Default is prog_name.RPT, in the same directory as the main executable. +// +// You can also pass "-" for stderr. +EXTERN_C BOOL APIENTRY +ExcHndlSetLogFileNameA(const char *szLogFileName); diff --git a/libs/drmingw/lib/win32/libexchndl.a b/libs/drmingw/lib/win32/libexchndl.a new file mode 100644 index 000000000..cd462a0df Binary files /dev/null and b/libs/drmingw/lib/win32/libexchndl.a differ diff --git a/libs/drmingw/lib/win32/libmgwhelp.a b/libs/drmingw/lib/win32/libmgwhelp.a new file mode 100644 index 000000000..330cb52fe Binary files /dev/null and b/libs/drmingw/lib/win32/libmgwhelp.a differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b04068609..87c4b6a65 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -103,7 +103,6 @@ set(SRB2_CORE_HEADERS m_swap.h md5.h mserv.h - http-mserv.h p5prof.h s_sound.h screen.h @@ -253,6 +252,8 @@ set(SRB2_CONFIG_HAVE_ZLIB ON CACHE BOOL "Enable zlib support.") set(SRB2_CONFIG_HAVE_GME ON CACHE BOOL "Enable GME support.") +set(SRB2_CONFIG_HAVE_DISCORDRPC OFF CACHE BOOL + "Enable Discord rich presence support.") set(SRB2_CONFIG_HAVE_CURL ON CACHE BOOL "Enable cURL support, used for downloading files via HTTP.") set(SRB2_CONFIG_HAVE_OPENMPT ON CACHE BOOL @@ -369,7 +370,7 @@ if(${SRB2_CONFIG_HAVE_GME}) if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES}) set(GME_FOUND ON) set(GME_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/gme/include) - if(${SRB2_SYSTEM_BITS} EQUAL 64) + if(${SRB2_SYSTEM_BITS} EQUAL 64) set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win64 -lgme") else() # 32-bit set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win32 -lgme") @@ -429,6 +430,32 @@ if(${SRB2_CONFIG_HAVE_MIXERX}) endif() endif() +if(${SRB2_CONFIG_HAVE_DISCORDRPC}) + if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES}) + set(DISCORDRPC_FOUND ON) + if(${SRB2_SYSTEM_BITS} EQUAL 64) + set(DISCORDRPC_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/discord-rpc/win64-dynamic/include) + set(DISCORDRPC_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/discord-rpc/win64-dynamic/lib -ldiscord-rpc") + else() # 32-bit + set(DISCORDRPC_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/discord-rpc/win32-dynamic/include) + set(DISCORDRPC_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/discord-rpc/win32-dynamic/lib -ldiscord-rpc") + endif() + else() + find_package(DiscordRPC) + endif() + if(${DISCORDRPC_FOUND}) + set(SRB2_HAVE_DISCORDRPC ON) + add_definitions(-DHAVE_DISCORDRPC) + set(SRB2_DISCORDRPC_SOURCES discord.c) + set(SRB2_DISCORDRPC_HEADERS discord.h) + prepend_sources(SRB2_DISCORDRPC_SOURCES) + prepend_sources(SRB2_DISCORDRPC_HEADERS) + source_group("Discord Rich Presence" FILES ${SRB2_DISCORDRPC_SOURCES} ${SRB2_DISCORDRPC_HEADERS}) + else() + message(WARNING "You have specified that Discord Rich Presence is available but it was not found.") + endif() +endif() + if(${SRB2_CONFIG_HAVE_ZLIB}) if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES}) set(ZLIB_FOUND ON) @@ -481,7 +508,7 @@ endif() if(${SRB2_CONFIG_HAVE_CURL}) if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES}) set(CURL_FOUND ON) - set(CURL_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/curl) + set(CURL_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/curl/include) if(${SRB2_SYSTEM_BITS} EQUAL 64) set(CURL_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/curl/lib64 -lcurl") else() # 32-bit diff --git a/src/Makefile b/src/Makefile index 67bb8e605..6342e937f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,10 +57,10 @@ # Compile a profile version, add 'PROFILEMODE=1' # Compile a debug version, add 'DEBUGMODE=1' # Compile for the testers group (they don't get to play unless we're watching *wink*), add 'TESTERS=1' -# Compile with extra warnings, add 'WARNINGMODE=1' +# Compile with less warnings, add 'RELAXWARNINGS=1' +# Generate compiler errors for most compiler warnings, add 'ERRORMODE=1' # Compile without NASM's tmap.nas, add 'NOASM=1' # Compile without 3D hardware support, add 'NOHW=1' -# Compile without 3D sound support, add 'NOHS=1' # Compile with GDBstubs, add 'RDB=1' # Compile without PNG, add 'NOPNG=1' # Compile without zlib, add 'NOZLIB=1' @@ -81,6 +81,58 @@ # ############################################################################# +ALL_SYSTEMS=\ + PANDORA\ + LINUX64\ + MINGW64\ + HAIKU\ + DUMMY\ + DJGPPDOS\ + MINGW\ + UNIX\ + LINUX\ + SOLARIS\ + FREEBSD\ + MACOSX\ + SDL\ + +# check for user specified system +ifeq (,$(filter $(ALL_SYSTEMS),$(.VARIABLES))) +ifeq ($(OS),Windows_NT) # all windows are Windows_NT... + + $(info Detected a Windows system, compiling for 32-bit MinGW SDL2...) + + # go for a 32-bit sdl mingw exe by default + MINGW=1 + SDL=1 + WINDOWSHELL=1 + +else # if you on the *nix + + system:=$(shell uname -s) + + ifeq ($(system),Linux) + new_system=LINUX + else + + $(error \ + Could not automatically detect your system,\ + try specifying a system manually) + + endif + + ifeq ($(shell getconf LONG_BIT),64) + system+=64-bit + new_system:=$(new_system)64 + endif + + $(info Detected $(system) ($(new_system))...) + $(new_system)=1 + +endif +endif + + # SRB2 data files D_DIR?=../bin/Resources D_FILES=$(D_DIR)/main.kart \ @@ -124,7 +176,6 @@ NOPNG=1 NOZLIB=1 NONET=1 NOHW=1 -NOHS=1 NOASM=1 NOIPX=1 EXENAME?=srb2dummy @@ -146,7 +197,6 @@ endif ifdef PANDORA NONX86=1 NOHW=1 -NOHS=1 endif ifndef NOOPENMPT @@ -228,13 +278,6 @@ else $(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o $(OBJDIR)/hw_batching.o endif -ifdef NOHS - OPTS+=-DNOHS -else - OPTS+=-DHW3SOUND - OBJS+=$(OBJDIR)/hw3sound.o -endif - OPTS += -DCOMPVERSION ifndef NONX86 @@ -348,6 +391,12 @@ CFLAGS+=-DHAVE_MINIUPNPC endif endif +ifdef HAVE_DISCORDRPC +LIBS+=-ldiscord-rpc +CFLAGS+=-DHAVE_DISCORDRPC +OBJS+=$(OBJDIR)/discord.o +endif + include blua/Makefile.cfg ifdef NOMD5 @@ -780,19 +829,6 @@ $(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \ $(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@ endif -ifndef NOHS -$(OBJDIR)/s_ds3d.o: hardware/s_ds3d/s_ds3d.c hardware/hw3dsdrv.h \ - hardware/hw_dll.h - $(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_ds3d.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_ds3d/s_ds3d.c - -$(OBJDIR)/s_fmod.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \ - hardware/hw_dll.h - $(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_fmod.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_fmod/s_fmod.c - -$(OBJDIR)/s_openal.o: hardware/s_openal/s_openal.c hardware/hw3dsdrv.h \ - hardware/hw_dll.h - $(CC) $(ARCHOPTS) -Os -o $(OBJDIR)/s_openal.o $(WFLAGS) -D_WINDOWS -mwindows -c hardware/s_openal/s_openal.c -endif endif endif diff --git a/src/Makefile.cfg b/src/Makefile.cfg index bf98dab38..3e011be5a 100644 --- a/src/Makefile.cfg +++ b/src/Makefile.cfg @@ -210,10 +210,7 @@ WFLAGS=-Wall ifndef GCC295 #WFLAGS+=-Wno-packed endif -ifdef ERRORMODE -WARNINGMODE=1 -endif -ifdef WARNINGMODE +ifndef RELAXWARNINGS WFLAGS+=-W #WFLAGS+=-Wno-sign-compare ifndef GCC295 diff --git a/src/blua/lvm.c b/src/blua/lvm.c index b654613f4..46a015c1e 100644 --- a/src/blua/lvm.c +++ b/src/blua/lvm.c @@ -322,8 +322,8 @@ static void Arith (lua_State *L, StkId ra, TValue *rb, case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break; case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break; case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break; - case TM_DIV: if (nc == 0) { lua_pushliteral(L, "divide by zero error"); lua_error(L); } else setnvalue(ra, luai_numdiv(nb, nc)); break; - case TM_MOD: if (nc == 0) { lua_pushliteral(L, "modulo by zero error"); lua_error(L); } else setnvalue(ra, luai_nummod(nb, nc)); break; + case TM_DIV: if (nc == 0) { luaG_runerror(L, "divide by zero error"); } else setnvalue(ra, luai_numdiv(nb, nc)); break; + case TM_MOD: if (nc == 0) { luaG_runerror(L, "modulo by zero error"); } else setnvalue(ra, luai_nummod(nb, nc)); break; case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break; case TM_UNM: setnvalue(ra, luai_numunm(nb)); break; case TM_AND: setnvalue(ra, luai_numand(nb, nc)); break; @@ -492,8 +492,7 @@ void luaV_execute (lua_State *L, int nexeccalls) { if (ttisnumber(rb) && ttisnumber(rc)) { lua_Number nb = nvalue(rb), nc = nvalue(rc); if (nc == 0) { - lua_pushliteral(L, "divide by zero error"); - lua_error(L); + luaG_runerror(L, "divide by zero error"); } else setnvalue(ra, luai_numdiv(nb, nc)); @@ -508,8 +507,7 @@ void luaV_execute (lua_State *L, int nexeccalls) { if (ttisnumber(rb) && ttisnumber(rc)) { lua_Number nb = nvalue(rb), nc = nvalue(rc); if (nc == 0) { - lua_pushliteral(L, "modulo by zero error"); - lua_error(L); + luaG_runerror(L, "modulo by zero error"); } else setnvalue(ra, luai_nummod(nb, nc)); diff --git a/src/config.h.in b/src/config.h.in index b42ebc5b6..1636a6731 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -29,7 +29,7 @@ /* Manually defined asset hashes for non-CMake builds * Last updated 2019 / 01 / 18 - Kart v1.0.2 - Main assets - * Last updated 2020 / 05 / 09 - Kart v1.2 - patch.kart + * Last updated 2020 / 08 / 30 - Kart v1.3 - patch.kart */ #define ASSET_HASH_MAIN_KART "00000000000000000000000000000000" diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 657e95681..bbf115124 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -53,6 +53,7 @@ #include "k_bot.h" #include "k_grandprix.h" #include "doomstat.h" +#include "s_sound.h" // sfx_syfail #ifndef NONET // cl loading screen @@ -60,6 +61,10 @@ #include "f_finale.h" #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // // NETWORKING // @@ -153,6 +158,9 @@ char connectedservername[MAXSERVERNAME]; /// \todo WORK! boolean acceptnewnode = true; +boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not +tic_t firstconnectattempttime = 0; + // engine // Must be a power of two @@ -1101,13 +1109,16 @@ static INT16 Consistancy(void); typedef enum { CL_SEARCHING, + CL_CHECKFILES, CL_DOWNLOADFILES, CL_ASKJOIN, + CL_LOADFILES, CL_WAITJOINRESPONSE, CL_DOWNLOADSAVEGAME, CL_CONNECTED, CL_ABORTED, CL_ASKFULLFILELIST, + CL_CONFIRMCONNECT, #ifdef HAVE_CURL CL_PREPAREHTTPFILES, CL_DOWNLOADHTTPFILES, @@ -1173,11 +1184,7 @@ static inline void CL_DrawConnectionStatus(void) if (!menuactive) // menu already draws its own fade V_DrawFadeScreen(0xFF00, 16); // force default - // Draw the bottom box. - M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort"); - - if (cl_mode != CL_DOWNLOADFILES + if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES #ifdef HAVE_CURL && cl_mode != CL_DOWNLOADHTTPFILES #endif @@ -1188,9 +1195,12 @@ static inline void CL_DrawConnectionStatus(void) // 15 pal entries total. const char *cltext; - if (!(cl_mode == CL_DOWNLOADSAVEGAME && lastfilenum != -1)) - for (i = 0; i < 16; ++i) - V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15)); + // Draw bottom box + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press ESC to abort"); + + for (i = 0; i < 16; ++i) + V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15)); switch (cl_mode) { @@ -1220,11 +1230,22 @@ static inline void CL_DrawConnectionStatus(void) cltext = M_GetText("Waiting to download game state..."); break; case CL_ASKFULLFILELIST: - cltext = M_GetText("This server has a LOT of files!"); + case CL_CHECKFILES: + cltext = M_GetText("Checking server addon list ..."); + break; + case CL_CONFIRMCONNECT: + cltext = ""; + break; + case CL_LOADFILES: + cltext = M_GetText("Loading server addons..."); break; case CL_ASKJOIN: case CL_WAITJOINRESPONSE: - cltext = M_GetText("Requesting to join..."); + if (serverisfull) + cltext = M_GetText("Server full, waiting for a slot..."); + else + cltext = M_GetText("Requesting to join..."); + break; #ifdef HAVE_CURL case CL_PREPAREHTTPFILES: @@ -1239,19 +1260,48 @@ static inline void CL_DrawConnectionStatus(void) } else { - if (lastfilenum != -1) + if (cl_mode == CL_LOADFILES) + { + INT32 totalfileslength; + INT32 loadcompletednum = 0; + INT32 i; + + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press ESC to abort"); + + //ima just count files here + for (i = 0; i < fileneedednum; i++) + if (fileneeded[i].status == FS_OPEN) + loadcompletednum++; + + // Loading progress + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP, "Loading server addons..."); + totalfileslength = (INT32)((loadcompletednum/(double)(fileneedednum)) * 256); + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 160); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, + va(" %2u/%2u Files",loadcompletednum,fileneedednum)); + } + else if (lastfilenum != -1) { INT32 dldlength; + INT32 totalfileslength; + UINT32 totaldldsize; static char tempname[28]; fileneeded_t *file = &fileneeded[lastfilenum]; char *filename = file->filename; + // Draw the bottom box. + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-58-8, 32, 1); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-14, V_YELLOWMAP, "Press ESC to abort"); + Net_GetNetStat(); dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256); if (dldlength > 256) dldlength = 256; - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111); - V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, dldlength, 8, 96); + + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, 256, 8, 111); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, dldlength, 8, 96); memset(tempname, 0, sizeof(tempname)); // offset filename to just the name only part @@ -1269,16 +1319,52 @@ static inline void CL_DrawConnectionStatus(void) strncpy(tempname, filename, sizeof(tempname)-1); } - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-22, V_YELLOWMAP, va(M_GetText("Downloading \"%s\""), tempname)); - V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, V_20TRANS|V_MONOSPACE, va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10)); - V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE, + V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-58, V_20TRANS|V_MONOSPACE, va("%3.1fK/s ", ((double)getbps)/1024)); + + // Download progress + + if (fileneeded[lastfilenum].currentsize != fileneeded[lastfilenum].totalsize) + totaldldsize = downloadcompletedsize+fileneeded[lastfilenum].currentsize; //Add in single file progress download if applicable + else + totaldldsize = downloadcompletedsize; + + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-14, V_YELLOWMAP, "Overall Download Progress"); + totalfileslength = (INT32)((totaldldsize/(double)totalfilesrequestedsize) * 256); + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175); + V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, totalfileslength, 8, 160); + + if (totalfilesrequestedsize>>20 >= 100) //display in MB if over 100MB + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, + va(" %4uM/%4uM",totaldldsize>>20,totalfilesrequestedsize>>20)); + else + V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, + va(" %4uK/%4uK",totaldldsize>>10,totalfilesrequestedsize>>10)); + + V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE, + va("%2u/%2u Files ",downloadcompletednum,totalfilesrequestednum)); } else - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, + { + INT32 i, animtime = ((ccstime / 4) & 15) + 16; + UINT8 palstart = (cl_mode == CL_SEARCHING) ? 128 : 160; + // 15 pal entries total. + + //Draw bottom box + M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press ESC to abort"); + + for (i = 0; i < 16; ++i) + V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15)); + + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP, M_GetText("Waiting to download files...")); + } } } @@ -1362,13 +1448,75 @@ static INT32 FindRejoinerNum(SINT8 node) return -1; } +static void +CopyCaretColors (char *p, const char *s, int n) +{ + char *t; + int m; + int c; + if (!n) + return; + while (( t = strchr(s, '^') )) + { + m = ( t - s ); + + if (m >= n) + { + memcpy(p, s, n); + return; + } + else + memcpy(p, s, m); + + p += m; + n -= m; + s += m; + + if (!n) + return; + + if (s[1]) + { + c = toupper(s[1]); + if (isdigit(c)) + c = 0x80 + ( c - '0' ); + else if (c >= 'A' && c <= 'F') + c = 0x80 + ( c - 'A' ); + else + c = 0; + + if (c) + { + *p++ = c; + n--; + + if (!n) + return; + } + else + { + if (n < 2) + break; + + memcpy(p, s, 2); + + p += 2; + n -= 2; + } + + s += 2; + } + else + break; + } + strncpy(p, s, n); +} + static void SV_SendServerInfo(INT32 node, tic_t servertime) { UINT8 *p; -#ifdef HAVE_CURL size_t mirror_length; const char *httpurl = cv_httpsource.string; -#endif netbuffer->packettype = PT_SERVERINFO; netbuffer->u.serverinfo._255 = 255; @@ -1403,8 +1551,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) (dedicated ? SV_DEDICATED : 0) ); - - strncpy(netbuffer->u.serverinfo.servername, cv_servername.string, + CopyCaretColors(netbuffer->u.serverinfo.servername, cv_servername.string, MAXSERVERNAME); strncpy(netbuffer->u.serverinfo.mapname, G_BuildMapName(gamemap), 7); @@ -1428,8 +1575,8 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) "%s", mapheaderinfo[gamemap-1]->lvlttl) < 0) { - // If there's an encoding error, send UNKNOWN, we accept that the above may be truncated - strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", sizeof netbuffer->u.serverinfo.maptitle); + // If there's an encoding error, send "Unknown", we accept that the above may be truncated + strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", sizeof netbuffer->u.serverinfo.maptitle); } } else @@ -1439,19 +1586,18 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) "%s %s", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl) < 0) { - // If there's an encoding error, send UNKNOWN, we accept that the above may be truncated - strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", sizeof netbuffer->u.serverinfo.maptitle); + // If there's an encoding error, send "Unknown", we accept that the above may be truncated + strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", sizeof netbuffer->u.serverinfo.maptitle); } } } else - strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", sizeof netbuffer->u.serverinfo.maptitle); + strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", sizeof netbuffer->u.serverinfo.maptitle); netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum; memset(netbuffer->u.serverinfo.httpsource, 0, MAX_MIRROR_LENGTH); -#ifdef HAVE_CURL mirror_length = strlen(httpurl); if (mirror_length > MAX_MIRROR_LENGTH) mirror_length = MAX_MIRROR_LENGTH; @@ -1461,7 +1607,6 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) strncpy(netbuffer->u.serverinfo.httpsource, "", mirror_length); netbuffer->u.serverinfo.httpsource[MAX_MIRROR_LENGTH-1] = '\0'; -#endif p = PutFileNeeded(0); @@ -1602,6 +1747,10 @@ static boolean SV_SendServerConfig(INT32 node) netbuffer->u.servercfg.playerisbot[i] = players[i].bot; } + netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value)); + netbuffer->u.servercfg.allownewplayer = cv_allownewplayer.value; + netbuffer->u.servercfg.discordinvites = (boolean)cv_discordinvites.value; + memcpy(netbuffer->u.servercfg.server_context, server_context, 8); op = p = netbuffer->u.servercfg.varlengthinputs; @@ -1801,7 +1950,7 @@ static void CL_LoadReceivedSavegame(void) if (strlen(mapheaderinfo[gamemap-1]->zonttl) > 0) CON_LogMessage(va(" %s", mapheaderinfo[gamemap-1]->zonttl)); else if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) - CON_LogMessage(M_GetText(" ZONE")); + CON_LogMessage(M_GetText(" Zone")); if (mapheaderinfo[gamemap-1]->actnum > 0) CON_LogMessage(va(" %d", mapheaderinfo[gamemap-1]->actnum)); } @@ -1905,57 +2054,30 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) M_SortServerList(); } -#ifdef HAVE_THREADS -struct Fetch_servers_ctx +void CL_UpdateServerList (void) { - int room; - int id; -}; + SL_ClearServerList(0); -static void -Fetch_servers_thread (struct Fetch_servers_ctx *ctx) -{ - msg_server_t *server_list; - - server_list = GetShortServersList(ctx->room, ctx->id); - - if (server_list) + if (!netgame && I_NetOpenSocket) { - I_lock_mutex(&ms_QueryId_mutex); + if (I_NetOpenSocket()) { - if (ctx->id != ms_QueryId) - { - free(server_list); - server_list = NULL; - } - } - I_unlock_mutex(ms_QueryId_mutex); - - if (server_list) - { - I_lock_mutex(&m_menu_mutex); - { - if (m_waiting_mode == M_WAITING_SERVERS) - m_waiting_mode = M_NOT_WAITING; - } - I_unlock_mutex(m_menu_mutex); - - I_lock_mutex(&ms_ServerList_mutex); - { - ms_ServerList = server_list; - } - I_unlock_mutex(ms_ServerList_mutex); + netgame = true; + multiplayer = true; } } - free(ctx); + // search for local servers + if (netgame) + SendAskInfo(BROADCASTADDR); } -#endif/*HAVE_THREADS*/ void CL_QueryServerList (msg_server_t *server_list) { INT32 i; + CL_UpdateServerList(); + for (i = 0; server_list[i].header.buffer[0]; i++) { // Make sure MS version matches our own, to @@ -1984,67 +2106,58 @@ void CL_QueryServerList (msg_server_t *server_list) } } } - -void CL_UpdateServerList(boolean internetsearch, INT32 room) -{ -#ifdef HAVE_THREADS - struct Fetch_servers_ctx *ctx; -#endif - - SL_ClearServerList(0); - - if (!netgame && I_NetOpenSocket) - { - if (I_NetOpenSocket()) - { - netgame = true; - multiplayer = true; - } - } - - // search for local servers - if (netgame) - SendAskInfo(BROADCASTADDR); - - if (internetsearch) - { -#ifdef HAVE_THREADS - ctx = malloc(sizeof *ctx); - - /* This called from M_Refresh so I don't use a mutex */ - m_waiting_mode = M_WAITING_SERVERS; - - I_lock_mutex(&ms_QueryId_mutex); - { - ctx->id = ms_QueryId; - } - I_unlock_mutex(ms_QueryId_mutex); - - ctx->room = room; - - I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx); -#else - msg_server_t *server_list; - - server_list = GetShortServersList(room, 0); - - if (server_list) - { - CL_QueryServerList(server_list); - free(server_list); - } -#endif - } -} - #endif // ifndef NONET +static void M_ConfirmConnect(event_t *ev) +{ +#ifndef NONET + if (ev->type == ev_keydown) + { + if (ev->data1 == ' ' || ev->data1 == 'y' || ev->data1 == KEY_ENTER || ev->data1 == gamecontrol[0][gc_accelerate][0] || ev->data1 == gamecontrol[0][gc_accelerate][1]) + { + if (totalfilesrequestednum > 0) + { +#ifdef HAVE_CURL + if (http_source[0] == '\0' || curl_failedwebdownload) +#endif + { + if (CL_SendFileRequest()) + { + cl_mode = CL_DOWNLOADFILES; + } + } +#ifdef HAVE_CURL + else + cl_mode = CL_PREPAREHTTPFILES; +#endif + } + else + cl_mode = CL_LOADFILES; + + M_ClearMenus(true); + } + else if (ev->data1 == 'n' || ev->data1 == KEY_ESCAPE|| ev->data1 == gamecontrol[0][gc_brake][0] || ev->data1 == gamecontrol[0][gc_brake][1]) + { + cl_mode = CL_ABORTED; + M_ClearMenus(true); + } + } +#else + (void)ev; +#endif +} + static boolean CL_FinishedFileList(void) { INT32 i; - CONS_Printf(M_GetText("Checking files...\n")); + char *downloadsize = NULL; + //CONS_Printf(M_GetText("Checking files...\n")); i = CL_CheckFiles(); - if (i == 3) // too many files + if (i == 4) // still checking ... + { + return true; + } + else if (i == 3) // too many files { D_QuitNetGame(); CL_Reset(); @@ -2073,7 +2186,21 @@ static boolean CL_FinishedFileList(void) return false; } else if (i == 1) - cl_mode = CL_ASKJOIN; + { + if (serverisfull) + { + M_StartMessage(M_GetText( + "This server is full!\n" + "\n" + "You may load server addons (if any), and wait for a slot.\n" + "\n" + "Press ACCEL to continue or BRAKE to cancel.\n\n" + ), M_ConfirmConnect, MM_EVENTHANDLER); + cl_mode = CL_CONFIRMCONNECT; + } + else + cl_mode = CL_LOADFILES; + } else { // must download something @@ -2098,14 +2225,61 @@ static boolean CL_FinishedFileList(void) ), NULL, MM_NOTHING); return false; } + } - if (CL_SendFileRequest()) - cl_mode = CL_DOWNLOADFILES; +#ifdef HAVE_CURL + if (!curl_failedwebdownload) +#endif + { +#ifndef NONET + downloadcompletednum = 0; + downloadcompletedsize = 0; + totalfilesrequestednum = 0; + totalfilesrequestedsize = 0; +#endif + + for (i = 0; i < fileneedednum; i++) + if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD) + { +#ifndef NONET + totalfilesrequestednum++; + totalfilesrequestedsize += fileneeded[i].totalsize; +#endif + } + +#ifndef NONET + if (totalfilesrequestedsize>>20 >= 100) + downloadsize = Z_StrDup(va("%uM",totalfilesrequestedsize>>20)); + else + downloadsize = Z_StrDup(va("%uK",totalfilesrequestedsize>>10)); +#endif + + if (serverisfull) + M_StartMessage(va(M_GetText( + "This server is full!\n" + "Download of %s additional content is required to join.\n" + "\n" + "You may download, load server addons, and wait for a slot.\n" + "\n" + "Press ACCEL to continue or BRAKE to cancel.\n\n" + ), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER); + else + M_StartMessage(va(M_GetText( + "Download of %s additional content is required to join.\n" + "\n" + "Press ACCEL to continue or BRAKE to cancel.\n\n" + ), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER); + + Z_Free(downloadsize); + cl_mode = CL_CONFIRMCONNECT; } #ifdef HAVE_CURL else { - cl_mode = CL_PREPAREHTTPFILES; + if (CL_SendFileRequest()) + { + cl_mode = CL_DOWNLOADFILES; + } } #endif } @@ -2145,16 +2319,7 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) // Quit here rather than downloading files and being refused later. if (serverlist[i].info.refusereason) { - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); - if (serverlist[i].info.refusereason == 1) - M_StartMessage(M_GetText("The server is not accepting\njoins for the moment.\n\nPress ESC\n"), NULL, MM_NOTHING); - else if (serverlist[i].info.refusereason == 2) - M_StartMessage(va(M_GetText("Maximum players reached: %d\n\nPress ESC\n"), serverlist[i].info.maxplayer), NULL, MM_NOTHING); - else - M_StartMessage(M_GetText("You can't join.\nI don't know why,\nbut you can't join.\n\nPress ESC\n"), NULL, MM_NOTHING); - return false; + serverisfull = true; } if (client) @@ -2168,7 +2333,6 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) if (serverlist[i].info.httpsource[0]) CONS_Printf("We received a http url from the server, however it will not be used as this build lacks curl support (%s)\n", serverlist[i].info.httpsource); #endif - D_ParseFileneeded(serverlist[i].info.fileneedednum, serverlist[i].info.fileneeded, 0); if (serverlist[i].info.kartvars & SV_LOTSOFADDONS) { @@ -2177,8 +2341,7 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) return true; } - if (!CL_FinishedFileList()) - return false; + cl_mode = CL_CHECKFILES; } else cl_mode = CL_ASKJOIN; // files need not be checked for the server. @@ -2187,13 +2350,12 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) } // Ask the info to the server (askinfo packet) - if (*asksent + NEWTICRATE < I_GetTime()) + if (I_GetTime() >= *asksent) { SendAskInfo(servernode); - *asksent = I_GetTime(); + *asksent = I_GetTime() + NEWTICRATE; } #else - (void)viams; (void)asksent; // No netgames, so we skip this state. cl_mode = CL_ASKJOIN; @@ -2216,7 +2378,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic { boolean waitmore; INT32 i; - + #ifdef NONET (void)tmpsave; #endif @@ -2230,28 +2392,30 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic case CL_ASKFULLFILELIST: if (cl_lastcheckedfilecount == UINT16_MAX) // All files retrieved - { - if (!CL_FinishedFileList()) - return false; - } - else if (fileneedednum != cl_lastcheckedfilecount || *asksent + NEWTICRATE < I_GetTime()) + cl_mode = CL_CHECKFILES; + else if (fileneedednum != cl_lastcheckedfilecount || I_GetTime() >= *asksent) { if (CL_AskFileList(fileneedednum)) { cl_lastcheckedfilecount = fileneedednum; - *asksent = I_GetTime(); + *asksent = I_GetTime() + NEWTICRATE; } } break; - + case CL_CHECKFILES: + if (!CL_FinishedFileList()) + return false; + break; #ifdef HAVE_CURL case CL_PREPAREHTTPFILES: if (http_source[0]) { for (i = 0; i < fileneedednum; i++) if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD) + { curl_transfers++; - + } + cl_mode = CL_DOWNLOADHTTPFILES; } break; @@ -2275,19 +2439,13 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic if (curl_failedwebdownload && !curl_transfers) { - if (!CL_FinishedFileList()) - break; - CONS_Printf("One or more files failed to download, falling back to internal downloader\n"); - if (CL_SendFileRequest()) - { - cl_mode = CL_DOWNLOADFILES; - break; - } + cl_mode = CL_CHECKFILES; + break; } if (!curl_transfers) - cl_mode = CL_ASKJOIN; // don't break case continue to cljoin request now + cl_mode = CL_LOADFILES; break; #endif @@ -2303,22 +2461,48 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic if (waitmore) break; // exit the case - cl_mode = CL_ASKJOIN; // don't break case continue to cljoin request now - /* FALLTHRU */ - + cl_mode = CL_LOADFILES; + break; + case CL_LOADFILES: + if (CL_LoadServerFiles()) + { + *asksent = 0; //This ensure the first join ask is right away + firstconnectattempttime = I_GetTime(); + cl_mode = CL_ASKJOIN; + } + break; case CL_ASKJOIN: - CL_LoadServerFiles(); -#ifndef NONET + if (firstconnectattempttime + NEWTICRATE*300 < I_GetTime() && !server) + { + CONS_Printf(M_GetText("5 minute wait time exceeded.\n")); + CONS_Printf(M_GetText("Network game synchronization aborted.\n")); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "5 minute wait time exceeded.\n" + "You may retry connection.\n" + "\n" + "Press ESC\n" + ), NULL, MM_NOTHING); + return false; + } // prepare structures to save the file // WARNING: this can be useless in case of server not in GS_LEVEL // but since the network layer doesn't provide ordered packets... CL_PrepareDownloadSaveGame(tmpsave); -#endif - if (CL_SendJoin()) + if (I_GetTime() >= *asksent && CL_SendJoin()) + { + *asksent = I_GetTime() + NEWTICRATE*3; cl_mode = CL_WAITJOINRESPONSE; + } + break; + case CL_WAITJOINRESPONSE: + if (I_GetTime() >= *asksent) + { + cl_mode = CL_ASKJOIN; + } break; - -#ifndef NONET case CL_DOWNLOADSAVEGAME: // At this state, the first (and only) needed file is the gamestate if (fileneeded[0].status == FS_FOUND) @@ -2326,13 +2510,12 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic // Gamestate is now handled within CL_LoadReceivedSavegame() CL_LoadReceivedSavegame(); cl_mode = CL_CONNECTED; + break; } // don't break case continue to CL_CONNECTED else break; -#endif - - case CL_WAITJOINRESPONSE: case CL_CONNECTED: + case CL_CONFIRMCONNECT: //logic is handled by M_ConfirmConnect default: break; @@ -2353,7 +2536,10 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) G_MapEventsToControls(&events[eventtail]); - if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1]) + if (cl_mode == CL_CONFIRMCONNECT) + D_ProcessEvents(); //needed for menu system to receive inputs + + if ((gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1]) || cl_mode == CL_ABORTED) { CONS_Printf(M_GetText("Network game synchronization aborted.\n")); // M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING); @@ -2384,6 +2570,13 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic F_TitleScreenDrawer(); } CL_DrawConnectionStatus(); +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + M_Drawer(); //Needed for drawing messageboxes on the connection screen +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif I_UpdateNoVsync(); // page flip or blit buffer if (moviemode) M_SaveFrame(); @@ -2447,9 +2640,10 @@ static void CL_ConnectToServer(void) ClearAdminPlayers(); K_ClearClientPowerLevels(); pnumnodes = 1; - oldtic = I_GetTime() - 1; + oldtic = 0; #ifndef NONET - asksent = (tic_t) - TICRATE; + asksent = 0; + firstconnectattempttime = I_GetTime(); i = SL_SearchServer(servernode); @@ -2814,6 +3008,8 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) // Reset the name sprintf(player_names[playernum], "Player %d", playernum+1); + player_name_changes[playernum] = 0; + if (IsPlayerAdmin(playernum)) { RemoveAdminPlayer(playernum); // don't stay admin after you're gone @@ -2859,6 +3055,14 @@ void CL_Reset(void) fileneedednum = 0; memset(fileneeded, 0, sizeof(fileneeded)); +#ifndef NONET + totalfilesrequestednum = 0; + totalfilesrequestedsize = 0; +#endif + firstconnectattempttime = 0; + serverisfull = false; + connectiontimeout = (tic_t)cv_nettimeout.value; //reset this temporary hack + #ifdef HAVE_CURL curl_failedwebdownload = false; curl_transfers = 0; @@ -3203,6 +3407,11 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) #endif } + if (msg == KICK_MSG_PLAYER_QUIT) + S_StartSound(NULL, sfx_leave); // intended leave + else + S_StartSound(NULL, sfx_syfail); // he he he + switch (msg) { case KICK_MSG_GO_AWAY: @@ -3366,18 +3575,26 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}}; consvar_t cv_netticbuffer = {"netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL }; +static void Joinable_OnChange(void); + +consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_CALL, CV_OnOff, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL}; + #ifdef VANILLAJOINNEXTROUND consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done #endif + static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}}; -consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE|CV_CALL, maxplayers_cons_t, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}}; consvar_t cv_joindelay = {"joindelay", "10", CV_SAVE, joindelay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}}; consvar_t cv_rejointimeout = {"rejointimeout", "Off", CV_SAVE|CV_FLOAT, rejointimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +// Here for dedicated servers +static CV_PossibleValue_t discordinvites_cons_t[] = {{0, "Admins Only"}, {1, "Everyone"}, {0, NULL}}; +consvar_t cv_discordinvites = {"discordinvites", "Everyone", CV_SAVE|CV_CALL, discordinvites_cons_t, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL}; + static CV_PossibleValue_t resynchattempts_cons_t[] = {{1, "MIN"}, {20, "MAX"}, {0, "No"}, {0, NULL}}; consvar_t cv_resynchattempts = {"resynchattempts", "5", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL }; @@ -3396,6 +3613,24 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum); static void Got_RemovePlayer(UINT8 **p, INT32 playernum); static void Got_AddBot(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) { @@ -3487,6 +3722,7 @@ void SV_ResetServer(void) } memset(splitscreen_partied, 0, sizeof splitscreen_partied); + memset(player_name_changes, 0, sizeof player_name_changes); mynode = 0; cl_packetmissed = false; @@ -3553,8 +3789,10 @@ void D_QuitNetGame(void) for (i = 0; i < MAXNETNODES; i++) if (nodeingame[i]) HSendPacket(i, true, 0, 0); - if (serverrunning && ms_RoomId > 0) +#ifdef MASTERSERVER + if (serverrunning && cv_advertise.value) UnregisterServer(); +#endif } else if (servernode > 0 && servernode < MAXNETNODES && nodeingame[(UINT8)servernode]) { @@ -3719,6 +3957,9 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)")); strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum)); + if (node != mynode) + S_StartSound(NULL, sfx_join); + // Merge join notification + IP to avoid clogging console/chat if (server && cv_showjoinaddress.value && I_GetNodeAddress) { @@ -3735,6 +3976,10 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (!rejoined) LUAh_PlayerJoin(newplayernum); + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } // Xcmd XD_REMOVEPLAYER @@ -3757,6 +4002,10 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) reason = READUINT8(*p); CL_RemovePlayer(pnum, reason); + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } // Xcmd XD_ADDBOT @@ -3950,8 +4199,10 @@ boolean SV_SpawnServer(void) if (netgame && I_NetOpenSocket) { I_NetOpenSocket(); - if (ms_RoomId > 0) +#ifdef MASTERSERVER + if (cv_advertise.value) RegisterServer(); +#endif } // non dedicated server just connect to itself @@ -3981,7 +4232,7 @@ void SV_StopServer(void) D_Clearticcmd(i); consoleplayer = 0; - cl_mode = CL_SEARCHING; + cl_mode = CL_ABORTED; maketic = gametic+1; neededtic = maketic; serverrunning = false; @@ -4015,7 +4266,7 @@ static void SV_SendRefuse(INT32 node, const char *reason) strcpy(netbuffer->u.serverrefuse.reason, reason); netbuffer->packettype = PT_SERVERREFUSE; - HSendPacket(node, true, 0, strlen(netbuffer->u.serverrefuse.reason) + 1); + HSendPacket(node, false, 0, strlen(netbuffer->u.serverrefuse.reason) + 1); Net_CloseConnection(node); } @@ -4187,6 +4438,7 @@ static void HandleTimeout(SINT8 node) */ static void HandleServerInfo(SINT8 node) { + char servername[MAXSERVERNAME]; // compute ping in ms const tic_t ticnow = I_GetTime(); const tic_t ticthen = (tic_t)LONG(netbuffer->u.serverinfo.time); @@ -4197,6 +4449,8 @@ static void HandleServerInfo(SINT8 node) [sizeof netbuffer->u.serverinfo.application - 1] = '\0'; netbuffer->u.serverinfo.gametypename [sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0'; + memcpy(servername, netbuffer->u.serverinfo.servername, MAXSERVERNAME); + CopyCaretColors(netbuffer->u.serverinfo.servername, servername, MAXSERVERNAME); SL_InsertServer(&netbuffer->u.serverinfo, node); } @@ -4309,13 +4563,24 @@ static void HandlePacketFromAwayNode(SINT8 node) if (!reason) I_Error("Out of memory!\n"); - D_QuitNetGame(); - CL_Reset(); - D_StartTitle(); + if (strstr(reason, "Maximum players reached")) + { + serverisfull = true; + //Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer + //We set it back to the value of cv_nettimeout.value in CL_Reset + connectiontimeout = NEWTICRATE*7; + cl_mode = CL_ASKJOIN; + free(reason); + break; + } M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"), reason), NULL, MM_NOTHING); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + free(reason); // Will be reset by caller. Signals refusal. @@ -4335,7 +4600,7 @@ static void HandlePacketFromAwayNode(SINT8 node) } SERVERONLY /// \note how would this happen? and is it doing the right thing if it does? - if (cl_mode != CL_WAITJOINRESPONSE) + if (!(cl_mode == CL_WAITJOINRESPONSE || cl_mode == CL_ASKJOIN)) break; if (client) @@ -4369,6 +4634,12 @@ static void HandlePacketFromAwayNode(SINT8 node) memcpy(server_context, netbuffer->u.servercfg.server_context, 8); } +#ifdef HAVE_DISCORDRPC + discordInfo.maxPlayers = netbuffer->u.servercfg.maxplayer; + discordInfo.joinsAllowed = netbuffer->u.servercfg.allownewplayer; + discordInfo.everyoneCanInvite = netbuffer->u.servercfg.discordinvites; +#endif + nodeingame[(UINT8)servernode] = true; serverplayer = netbuffer->u.servercfg.serverplayer; doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum); @@ -5768,7 +6039,9 @@ void NetKeepAlive(void) GetPackets(); +#ifdef MASTERSERVER MasterClient_Ticker(); +#endif if (client) { @@ -5825,7 +6098,9 @@ void NetUpdate(void) // client send the command after a receive of the server // the server send before because in single player is beter +#ifdef MASTERSERVER MasterClient_Ticker(); // Acking the Master Server +#endif if (client) { diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 4a3c5baa6..2f1caa82e 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -367,6 +367,11 @@ typedef struct char server_context[8]; // Unique context id, generated at server startup. + // Discord info (always defined for net compatibility) + UINT8 maxplayer; + boolean allownewplayer; + boolean discordinvites; + UINT8 varlengthinputs[0]; // Playernames and netvars } ATTRPACK serverconfig_pak; @@ -592,6 +597,10 @@ typedef enum } kickreason_t; +/* the max number of name changes in some time period */ +#define MAXNAMECHANGES (5) +#define NAMECHANGERATE (60*TICRATE) + extern boolean server; extern boolean serverrunning; #define client (!server) @@ -619,6 +628,8 @@ extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed; extern consvar_t cv_joinnextround; #endif +extern consvar_t cv_discordinvites; + // Used in d_net, the only dependence tic_t ExpandTics(INT32 low, tic_t basetic); void D_ClientServerInit(void); @@ -643,7 +654,7 @@ void CL_Reset(void); void CL_ClearPlayer(INT32 playernum); void CL_RemovePlayer(INT32 playernum, kickreason_t reason); void CL_QueryServerList(msg_server_t *list); -void CL_UpdateServerList(boolean internetsearch, INT32 room); +void CL_UpdateServerList(void); // Is there a game running boolean Playing(void); diff --git a/src/d_main.c b/src/d_main.c index e3c334d92..bc46fc0a3 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -59,7 +59,6 @@ #include "m_cheat.h" #include "y_inter.h" #include "p_local.h" // chasecam -#include "mserv.h" // ms_RoomId #include "m_misc.h" // screenshot functionality #include "dehacked.h" // Dehacked list test #include "m_cond.h" // condition initialization @@ -96,6 +95,10 @@ int VERSION; int SUBVERSION; +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // platform independant focus loss UINT8 window_notinfocus = false; @@ -901,6 +904,13 @@ void D_SRB2Loop(void) #endif LUA_Step(); + +#ifdef HAVE_DISCORDRPC + if (! dedicated) + { + Discord_RunCallbacks(); + } +#endif } } @@ -1078,9 +1088,23 @@ static void ChangeDirForUrlHandler(void) // Identify the SRB2 version, and IWAD file to use. // ========================================================================== +static boolean AddIWAD(void) +{ + char * path = va(pandf,srb2path,"main.kart"); + + if (FIL_ReadFileOK(path)) + { + D_AddFile(path, startupiwads); + return true; + } + else + { + return false; + } +} + static void IdentifyVersion(void) { - char *mainresource; const char *srb2waddir = NULL; #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) @@ -1098,36 +1122,19 @@ static void IdentifyVersion(void) if (getcwd(srb2path, 256) != NULL) srb2waddir = srb2path; else - { srb2waddir = "."; - } } -#if defined (macintosh) && !defined (HAVE_SDL) - // cwd is always "/" when app is dbl-clicked - if (!stricmp(srb2waddir, "/")) - srb2waddir = I_GetWadDir(); -#endif - // Commercial. - mainresource = malloc(strlen(srb2waddir)+1+9+1); - if (mainresource == NULL) - I_Error("No more free memory to look in %s", srb2waddir); - else - sprintf(mainresource, pandf, srb2waddir, "main.kart"); + // Load the IWAD + if (! AddIWAD()) + { + I_Error("MAIN.KART not found! Expected in %s\n", srb2waddir); + } // will be overwritten in case of -cdrom or unix/win home snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir); configfile[sizeof configfile - 1] = '\0'; - // Load the IWAD - if (mainresource != NULL && FIL_ReadFileOK(mainresource)) - D_AddFile(mainresource, startupiwads); - else - I_Error("main.kart not found! Expected in %s, ss file: %s \n", srb2waddir, mainresource); - - if (mainresource) - free(mainresource); - // if you change the ordering of this or add/remove a file, be sure to update the md5 // checking in D_SRB2Main @@ -1662,17 +1669,6 @@ void D_SRB2Main(void) CONS_Printf("ST_Init(): Init status bar.\n"); ST_Init(); - if (M_CheckParm("-room")) - { - if (!M_IsNextParm()) - I_Error("usage: -room \nCheck the Master Server's webpage for room ID numbers.\n"); - ms_RoomId = atoi(M_GetNextParm()); - -#ifdef UPDATE_ALERT - GetMODVersion_Console(); -#endif - } - // Set up splitscreen players before joining! if (!dedicated && (M_CheckParm("-splitscreen") && M_IsNextParm())) { @@ -1890,6 +1886,13 @@ void D_SRB2Main(void) if (!P_LoadLevel(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 65a9db6f5..a93e86ac0 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -64,6 +64,10 @@ #define CV_RESTRICT 0 #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + // ------ // protos // ------ @@ -89,6 +93,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); @@ -748,10 +753,13 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_showviewpointtext); #ifdef SEENAMES - CV_RegisterVar(&cv_allowseenames); + CV_RegisterVar(&cv_allowseenames); #endif CV_RegisterVar(&cv_dummyconsvar); + + CV_RegisterVar(&cv_discordinvites); + RegisterNetXCmd(XD_DISCORD, Got_DiscordInfo); } // ========================================================================= @@ -1038,6 +1046,12 @@ 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); +#endif } /** Checks if a name (as received from another player) is okay. @@ -1255,6 +1269,8 @@ static void SetPlayerName(INT32 playernum, char *newname) if (netgame) HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false); + player_name_changes[playernum]++; + strcpy(player_names[playernum], newname); demo_extradata[playernum] |= DXD_NAME; } @@ -1455,7 +1471,12 @@ static void SendNameAndColor(UINT8 n) snacpending[n]++; // Don't change name if muted - if (cv_mute.value && !(server || IsPlayerAdmin(playernum))) + if (player_name_changes[playernum] >= MAXNAMECHANGES) + { + CV_StealthSet(&cv_playername[n], player_names[playernum]); + HU_AddChatText("\x85*You must wait to change your name again", false); + } + else if (cv_mute.value && !(server || IsPlayerAdmin(playernum))) CV_StealthSet(&cv_playername[n], player_names[playernum]); else // Cleanup name if changing it CleanupPlayerName(playernum, cv_playername[n].zstring); @@ -1491,6 +1512,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) UINT16 color, followercolor; UINT8 skin; SINT8 follower; + SINT8 localplayer = -1; UINT8 i; #ifdef PARANOIA @@ -1509,7 +1531,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) I_Error("snacpending[%d] negative!", i); #endif - // i is now player screen id + localplayer = i; break; } } @@ -1590,8 +1612,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) const INT32 forcedskin = cv_forceskin.value; SetPlayerSkinByNum(playernum, forcedskin); - if (playernum == g_localplayers[i]) - CV_StealthSet(&cv_skin[i], skins[forcedskin].name); + if (localplayer != -1) + CV_StealthSet(&cv_skin[localplayer], skins[forcedskin].name); } else SetPlayerSkinByNum(playernum, skin); @@ -1602,6 +1624,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) // set follower SetFollower(playernum, follower); + +#ifdef HAVE_DISCORDRPC + if (playernum == consoleplayer) + DRPC_UpdatePresence(); +#endif } void SendWeaponPref(UINT8 n) @@ -2865,6 +2892,10 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) if (demo.recording) // Okay, level loaded, character spawned and skinned, G_BeginRecording(); // I AM NOW READY TO RECORD. demo.deferstart = true; + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } static void Command_Pause(void) @@ -3799,7 +3830,7 @@ static void Command_RunSOC(void) static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum) { char filename[256]; - filestatus_t ncs = FS_NOTFOUND; + filestatus_t ncs = FS_NOTCHECKED; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { @@ -3937,7 +3968,7 @@ static void Command_Addfile(void) static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum) { char filename[241]; - filestatus_t ncs = FS_NOTFOUND; + filestatus_t ncs = FS_NOTCHECKED; UINT8 md5sum[16]; boolean kick = false; boolean toomany = false; @@ -3996,7 +4027,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum) static void Got_Addfilecmd(UINT8 **cp, INT32 playernum) { char filename[241]; - filestatus_t ncs = FS_NOTFOUND; + filestatus_t ncs = FS_NOTCHECKED; UINT8 md5sum[16]; READSTRINGN(*cp, filename, 240); @@ -4289,6 +4320,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. @@ -5603,3 +5638,26 @@ static void KartEliminateLast_OnChange(void) P_CheckRacers(); } + +void Got_DiscordInfo(UINT8 **p, INT32 playernum) +{ + if (playernum != serverplayer /*&& !IsPlayerAdmin(playernum)*/) + { + // protect against hacked/buggy client + CONS_Alert(CONS_WARNING, M_GetText("Illegal Discord info command received from %s\n"), player_names[playernum]); + if (server) + 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); + + DRPC_UpdatePresence(); +#else + (*p) += 3; +#endif +} diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 30a6e845b..e7c7ad206 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -161,6 +161,7 @@ typedef enum XD_CANCELPARTYINVITE, // 31 XD_GIVEITEM, // 32 XD_ADDBOT, // 33 + XD_DISCORD, // 34 MAXNETXCMD } netxcmd_t; diff --git a/src/d_netfil.c b/src/d_netfil.c index 66b0ff650..5ca99508a 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -118,6 +118,10 @@ static pauseddownload_t *pauseddownload = NULL; #ifndef NONET // for cl loading screen INT32 lastfilenum = -1; +INT32 downloadcompletednum = 0; +UINT32 downloadcompletedsize = 0; +INT32 totalfilesrequestednum = 0; +UINT32 totalfilesrequestedsize = 0; #endif #ifdef HAVE_CURL @@ -156,7 +160,7 @@ UINT8 *PutFileNeeded(UINT16 firstfile) char wadfilename[MAX_WADPATH] = ""; UINT8 filestatus; - for (i = mainwads; i < numwadfiles; i++) + for (i = mainwads+1; i < numwadfiles; i++) //mainwads+1, otherwise we start on the first mainwad { // If it has only music/sound lumps, don't put it in the list if (!wadfiles[i]->important) @@ -222,7 +226,7 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 fi p = (UINT8 *)fileneededstr; for (i = firstfile; i < fileneedednum; i++) { - fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet + fileneeded[i].status = FS_NOTCHECKED; // We haven't even started looking for the file yet fileneeded[i].justdownloaded = false; filestatus = READUINT8(p); // The first byte is the file status fileneeded[i].willsend = (UINT8)(filestatus >> 4); @@ -416,15 +420,17 @@ boolean PT_RequestFile(INT32 node) * \return 0 if some files are missing * 1 if all files exist * 2 if some already loaded files are not requested or are in a different order + * 3 too many files, over WADLIMIT + * 4 still checking, continuing next tic * */ INT32 CL_CheckFiles(void) { INT32 i, j; char wadfilename[MAX_WADPATH]; - INT32 ret = 1; size_t packetsize = 0; - size_t filestoget = 0; + size_t filestoload = 0; + boolean downloadrequired = false; // if (M_CheckParm("-nofiles")) // return 1; @@ -441,7 +447,7 @@ INT32 CL_CheckFiles(void) if (modifiedgame) { CONS_Debug(DBG_NETPLAY, "game is modified; only doing basic checks\n"); - for (i = 0, j = mainwads; i < fileneedednum || j < numwadfiles;) + for (i = 0, j = mainwads+1; i < fileneedednum || j < numwadfiles;) { if (j < numwadfiles && !wadfiles[j]->important) { @@ -470,10 +476,19 @@ INT32 CL_CheckFiles(void) for (i = 0; i < fileneedednum; i++) { + if (fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_FALLBACK) + downloadrequired = true; + + if (fileneeded[i].status == FS_FOUND || fileneeded[i].status == FS_NOTFOUND) + filestoload++; + + if (fileneeded[i].status != FS_NOTCHECKED) //since we're running this over multiple tics now, its possible for us to come across files checked in previous tics + continue; + CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename); // Check in already loaded files - for (j = mainwads; wadfiles[j]; j++) + for (j = mainwads+1; wadfiles[j]; j++) { nameonly(strcpy(wadfilename, wadfiles[j]->filename)); if (!stricmp(wadfilename, fileneeded[i].filename) && @@ -481,36 +496,35 @@ INT32 CL_CheckFiles(void) { CONS_Debug(DBG_NETPLAY, "already loaded\n"); fileneeded[i].status = FS_OPEN; - break; + return 4; } } - if (fileneeded[i].status != FS_NOTFOUND) - continue; packetsize += nameonlylength(fileneeded[i].filename) + 22; - if (mainwads+filestoget >= MAX_WADFILES) - return 3; - - filestoget++; - fileneeded[i].status = findfile(fileneeded[i].filename, fileneeded[i].md5sum, true); CONS_Debug(DBG_NETPLAY, "found %d\n", fileneeded[i].status); - if (fileneeded[i].status != FS_FOUND) - ret = 0; + return 4; } - return ret; + + //now making it here means we've checked the entire list and no FS_NOTCHECKED files remain + if (numwadfiles+filestoload > MAX_WADFILES) + return 3; + else if (downloadrequired) + return 0; //some stuff is FS_NOTFOUND, needs download + else + return 1; //everything is FS_OPEN or FS_FOUND, proceed to loading } // Load it now -void CL_LoadServerFiles(void) +boolean CL_LoadServerFiles(void) { INT32 i; // if (M_CheckParm("-nofiles")) // return; - for (i = 1; i < fileneedednum; i++) + for (i = 0; i < fileneedednum; i++) { if (fileneeded[i].status == FS_OPEN) continue; // Already loaded @@ -519,6 +533,7 @@ void CL_LoadServerFiles(void) P_AddWadFile(fileneeded[i].filename); G_SetGameModified(true, false); fileneeded[i].status = FS_OPEN; + return false; } else if (fileneeded[i].status == FS_MD5SUMBAD) I_Error("Wrong version of file %s", fileneeded[i].filename); @@ -544,6 +559,7 @@ void CL_LoadServerFiles(void) fileneeded[i].status, s); } } + return true; } void AddLuaFileTransfer(const char *filename, const char *mode) @@ -1374,6 +1390,11 @@ void PT_FileFragment(void) netbuffer->packettype = PT_HASLUAFILE; HSendPacket(servernode, true, 0, 0); } + +#ifndef NONET + downloadcompletednum++; + downloadcompletedsize += file->totalsize; +#endif } } else // Already received @@ -1732,6 +1753,8 @@ void CURLGetFile(void) { nameonly(curl_realname); CONS_Printf(M_GetText("Finished downloading %s\n"), curl_realname); + downloadcompletednum++; + downloadcompletedsize += curl_curfile->totalsize; curl_curfile->status = FS_FOUND; fclose(curl_curfile->file); } diff --git a/src/d_netfil.h b/src/d_netfil.h index b7f80bdc6..31f9bc507 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -27,6 +27,7 @@ typedef enum typedef enum { + FS_NOTCHECKED, FS_NOTFOUND, FS_FOUND, FS_REQUESTED, @@ -61,6 +62,10 @@ extern char downloaddir[512]; #ifndef NONET extern INT32 lastfilenum; +extern INT32 downloadcompletednum; +extern UINT32 downloadcompletedsize; +extern INT32 totalfilesrequestednum; +extern UINT32 totalfilesrequestedsize; #endif #ifdef HAVE_CURL @@ -74,7 +79,7 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr, UINT16 fi void CL_PrepareDownloadSaveGame(const char *tmpsave); INT32 CL_CheckFiles(void); -void CL_LoadServerFiles(void); +boolean CL_LoadServerFiles(void); void AddRamToSendQueue(INT32 node, void *data, size_t size, freemethod_t freemethod, UINT8 fileid); diff --git a/src/dehacked.c b/src/dehacked.c index 6925b02f1..392bfd5df 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -10362,7 +10362,6 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_TWINKLECARTAMBIENCE", "MT_EXPLODINGBARREL", "MT_MERRYHORSE", - "MT_ARIDTOAD", "MT_BLUEFRUIT", "MT_ORANGEFRUIT", "MT_REDFRUIT", @@ -10373,6 +10372,7 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_BOOSTPROMPT", "MT_BOOSTOFF", "MT_BOOSTON", + "MT_ARIDTOAD", "MT_LIZARDMAN", "MT_LIONMAN", @@ -12689,8 +12689,6 @@ static inline int lib_getenum(lua_State *L) if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word); // DYNAMIC variables too!! - // Try not to add anything that would break netgames or timeattack replays here. - // You know, like consoleplayer, displayplayer, secondarydisplayplayer, or gametime. return LUA_PushGlobals(L, word); } diff --git a/src/discord.c b/src/discord.c new file mode 100644 index 000000000..fdbdcb415 --- /dev/null +++ b/src/discord.c @@ -0,0 +1,712 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2018-2020 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file discord.h +/// \brief Discord Rich Presence handling + +#ifdef HAVE_DISCORDRPC + +#ifdef HAVE_CURL +#include +#endif // HAVE_CURL + +#include "i_system.h" +#include "d_clisrv.h" +#include "d_netcmd.h" +#include "i_net.h" +#include "g_game.h" +#include "p_tick.h" +#include "m_menu.h" // gametype_cons_t +#include "r_things.h" // skins +#include "mserv.h" // cv_advertise +#include "z_zone.h" +#include "byteptr.h" + +#include "discord.h" +#include "doomdef.h" + +// Feel free to provide your own, if you care enough to create another Discord app for this :P +#define DISCORD_APPID "503531144395096085" + +// length of IP strings +#define IP_SIZE 21 + +consvar_t cv_discordrp = {"discordrp", "On", CV_SAVE|CV_CALL, CV_OnOff, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_discordstreamer = {"discordstreamer", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_discordasks = {"discordasks", "Yes", CV_SAVE|CV_CALL, CV_YesNo, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL}; + +struct discordInfo_s discordInfo; + +discordRequest_t *discordRequestList = NULL; + +#ifdef HAVE_CURL +struct SelfIPbuffer +{ + CURL *curl; + char *pointer; + size_t length; +}; + +static char self_ip[IP_SIZE]; +#endif // HAVE_CURL + +/*-------------------------------------------------- + static char *DRPC_XORIPString(const char *input) + + Simple XOR encryption/decryption. Not complex or + very secretive because we aren't sending anything + that isn't easily accessible via our Master Server anyway. +--------------------------------------------------*/ +static char *DRPC_XORIPString(const char *input) +{ + const UINT8 xor[IP_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 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); +} + +/*-------------------------------------------------- + void DRPC_Init(void) + + See header file for description. +--------------------------------------------------*/ +void DRPC_Init(void) +{ + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + + handlers.ready = DRPC_HandleReady; + handlers.disconnected = DRPC_HandleDisconnect; + handlers.errored = DRPC_HandleError; + handlers.joinGame = DRPC_HandleJoin; + handlers.joinRequest = DRPC_HandleJoinRequest; + + Discord_Initialize(DISCORD_APPID, &handlers, 1, NULL); + I_AddExitFunc(Discord_Shutdown); + DRPC_UpdatePresence(); +} + +#ifdef HAVE_CURL +/*-------------------------------------------------- + static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata) + + Writing function for use with curl. Only intended to be used with simple text. + + Input Arguments:- + s - Data to write + size - Always 1. + n - Length of data + userdata - Passed in from CURLOPT_WRITEDATA, intended to be SelfIPbuffer + + Return:- + Number of bytes wrote in this pass. +--------------------------------------------------*/ +static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata) +{ + struct SelfIPbuffer *buffer; + size_t newlength; + + buffer = userdata; + + newlength = buffer->length + size*n; + buffer->pointer = realloc(buffer->pointer, newlength+1); + + memcpy(buffer->pointer + buffer->length, s, size*n); + + buffer->pointer[newlength] = '\0'; + buffer->length = newlength; + + return size*n; +} +#endif // HAVE_CURL + +/*-------------------------------------------------- + static const char *DRPC_GetServerIP(void) + + Retrieves the IP address of the server that you're + connected to. Will attempt to use curl for getting your + own IP address, if it's not yours. +--------------------------------------------------*/ +static const char *DRPC_GetServerIP(void) +{ + const char *address; + + // If you're connected + if (I_GetNodeAddress && (address = I_GetNodeAddress(servernode)) != NULL) + { + if (strcmp(address, "self")) + { + // We're not the server, so we could successfully get the IP! + // No need to do anything else :) + return address; + } + } + +#ifdef HAVE_CURL + // This is a little bit goofy, but + // there's practically no good way to get your own public IP address, + // so we've gotta break out curl for this :V + if (!self_ip[0]) + { + CURL *curl; + + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + + if (curl) + { + // The API to get your public IP address from. + // Picked because it's stupid simple and it's been up for a long time. + const char *api = "http://ip4only.me/api/"; + + struct SelfIPbuffer buffer; + CURLcode success; + + buffer.length = 0; + buffer.pointer = malloc(buffer.length+1); + buffer.pointer[0] = '\0'; + + curl_easy_setopt(curl, CURLOPT_URL, api); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DRPC_WriteServerIP); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + success = curl_easy_perform(curl); + + if (success == CURLE_OK) + { + char *tmp; + tmp = strtok(buffer.pointer, ","); + + if (!strcmp(tmp, "IPv4")) // ensure correct type of IP + { + tmp = strtok(NULL, ","); + strncpy(self_ip, tmp, IP_SIZE); // Yay, we have the IP :) + } + } + + free(buffer.pointer); + curl_easy_cleanup(curl); + } + + curl_global_cleanup(); + } + + if (self_ip[0]) + return self_ip; + else +#endif // HAVE_CURL + return NULL; // Could not get your IP for whatever reason, so we cannot do Discord invites +} + +/*-------------------------------------------------- + void DRPC_EmptyRequests(void) + + Empties the request list. Any existing requests + will get an ignore reply. +--------------------------------------------------*/ +static void DRPC_EmptyRequests(void) +{ + while (discordRequestList != NULL) + { + Discord_Respond(discordRequestList->userID, DISCORD_REPLY_IGNORE); + DRPC_RemoveRequest(discordRequestList); + } +} + +/*-------------------------------------------------- + void DRPC_UpdatePresence(void) + + See header file for description. +--------------------------------------------------*/ +void DRPC_UpdatePresence(void) +{ + char detailstr[48+1]; + + char mapimg[8+1]; + char mapname[5+21+21+2+1]; + + char charimg[4+SKINNAMESIZE+1]; + char charname[11+SKINNAMESIZE+1]; + + boolean joinSecretSet = false; + + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + + if (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 (cv_advertise.value) + { + discordPresence.state = "Public"; + } + else + { + discordPresence.state = "Private"; + } + + discordPresence.partyId = server_context; // Thanks, whoever gave us Mumble support, for implementing the EXACT thing Discord wanted for this field! + discordPresence.partySize = D_NumPlayers(); // Players in server + discordPresence.partyMax = discordInfo.maxPlayers; // Max players + + if (DRPC_InvitesAreAllowed() == true) + { + const char *join; + + // Grab the host's IP for joining. + if ((join = DRPC_GetServerIP()) != NULL) + { + char *xorjoin = DRPC_XORIPString(join); + discordPresence.joinSecret = xorjoin; + free(xorjoin); + + joinSecretSet = true; + } + } + } + else + { + // Reset discord info if you're not in a place that uses it! + // Important for if you join a server that compiled without HAVE_DISCORDRPC, + // so that you don't ever end up using bad information from another server. + memset(&discordInfo, 0, sizeof(discordInfo)); + + // Offline info + if (Playing()) + discordPresence.state = "Offline"; + else if (demo.playback && !demo.title) + discordPresence.state = "Watching Replay"; + else + discordPresence.state = "Menu"; + } + + // Gametype info + if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING) && Playing()) + { + if (modeattacking) + discordPresence.details = "Time Attack"; + else + { + snprintf(detailstr, 48, "%s%s%s", + gametype_cons_t[gametype].strvalue, + (gametype == GT_RACE) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", + (encoremode == true) ? " | Encore" : "" + ); + discordPresence.details = detailstr; + } + } + + if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) // Map info + && !(demo.playback && demo.title)) + { + if ((gamemap >= 1 && gamemap <= 60) // supported race maps + || (gamemap >= 136 && gamemap <= 164)) // supported battle maps + { + snprintf(mapimg, 8, "%s", G_BuildMapName(gamemap)); + strlwr(mapimg); + discordPresence.largeImageKey = mapimg; // Map image + } + else if (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU) + { + // Hell map, use the method that got you here :P + discordPresence.largeImageKey = "miscdice"; + } + else + { + // This is probably a custom map! + discordPresence.largeImageKey = "mapcustom"; + } + + if (mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU) + { + // Hell map, hide the name + discordPresence.largeImageText = "Map: ???"; + } + else + { + // Map name on tool tip + snprintf(mapname, 48, "Map: %s", G_BuildMapTitle(gamemap)); + discordPresence.largeImageText = mapname; + } + + if (gamestate == GS_LEVEL && Playing()) + { + const time_t currentTime = time(NULL); + const time_t mapTimeStart = currentTime - ((leveltime + (modeattacking ? starttime : 0)) / TICRATE); + + discordPresence.startTimestamp = mapTimeStart; + + if (timelimitintics > 0) + { + const time_t mapTimeEnd = mapTimeStart + ((timelimitintics + starttime + 1) / TICRATE); + discordPresence.endTimestamp = mapTimeEnd; + } + } + } + else if (gamestate == GS_VOTING) + { + discordPresence.largeImageKey = ((gametype == GT_BATTLE) ? "miscredplanet" : "miscblueplanet"); + discordPresence.largeImageText = "Voting"; + } + else + { + discordPresence.largeImageKey = "misctitle"; + discordPresence.largeImageText = "Title Screen"; + } + + // Character info + if (Playing() && playeringame[consoleplayer] && !players[consoleplayer].spectator) + { + // Supported skin names + static const char *supportedSkins[] = { + // base game + "sonic", + "tails", + "knuckles", + "eggman", + "metalsonic", + // bonus chars + "flicky", + "motobug", + "amy", + "mighty", + "ray", + "espio", + "vector", + "chao", + "gamma", + "chaos", + "shadow", + "rouge", + "herochao", + "darkchao", + "cream", + "omega", + "blaze", + "silver", + "wonderboy", + "arle", + "nights", + "sakura", + "ulala", + "beat", + "vyse", + "aiai", + "kiryu", + "aigis", + "miku", + "doom", + NULL + }; + + boolean customChar = true; + UINT8 checkSkin = 0; + + // Character image + while (supportedSkins[checkSkin] != NULL) + { + if (!strcmp(skins[players[consoleplayer].skin].name, supportedSkins[checkSkin])) + { + snprintf(charimg, 21, "char%s", supportedSkins[checkSkin]); + discordPresence.smallImageKey = charimg; + customChar = false; + break; + } + + checkSkin++; + } + + if (customChar == true) + { + // Use the custom character icon! + discordPresence.smallImageKey = "charcustom"; + } + + snprintf(charname, 28, "Character: %s", skins[players[consoleplayer].skin].realname); + discordPresence.smallImageText = charname; // Character name + } + + if (joinSecretSet == false) + { + // Not able to join? Flush the request list, if it exists. + DRPC_EmptyRequests(); + } + + Discord_UpdatePresence(&discordPresence); +} + +#endif // HAVE_DISCORDRPC diff --git a/src/discord.h b/src/discord.h new file mode 100644 index 000000000..a6bb1134a --- /dev/null +++ b/src/discord.h @@ -0,0 +1,80 @@ +// 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 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__ diff --git a/src/doomdata.h b/src/doomdata.h index fde0a7d8e..ed01f42c6 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -220,4 +220,11 @@ typedef struct #define NUMMAPS 1035 +/* slope thing types */ +enum +{ + FLOOR_SLOPE_THING = 777, + CEILING_SLOPE_THING = 778, +}; + #endif // __DOOMDATA__ diff --git a/src/doomdef.h b/src/doomdef.h index f0347c716..b1960b507 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -29,7 +29,6 @@ // Use Mixer interface? #ifdef HAVE_MIXER #define SOUND SOUND_MIXER - #define NOHS // No HW3SOUND #ifdef HW3SOUND #undef HW3SOUND #endif @@ -45,7 +44,6 @@ // Use FMOD? #ifdef HAVE_FMOD #define SOUND SOUND_FMOD - #define NOHS // No HW3SOUND #ifdef HW3SOUND #undef HW3SOUND #endif @@ -62,10 +60,6 @@ #if !defined (HWRENDER) && !defined (NOHW) #define HWRENDER #endif -// judgecutor: 3D sound support -#if !defined(HW3SOUND) && !defined (NOHS) -#define HW3SOUND -#endif #endif #ifdef _WIN32 @@ -165,32 +159,13 @@ extern char logfilename[1024]; // Please change to apply to your modification (we don't want everyone asking where your mod is on SRB2.org!). #define UPDATE_ALERT_STRING \ "A new update is available for SRB2Kart.\n"\ -"Please visit mb.srb2.org to download it.\n"\ +"Please visit kartkrew.org to download it.\n"\ "\n"\ "You are using version: %s\n"\ "The newest version is: %s\n"\ "\n"\ -"This update is required for online\n"\ -"play using the Master Server.\n"\ -"You will not be able to connect to\n"\ -"the Master Server until you update to\n"\ -"the newest version of the game.\n"\ -"\n"\ "(Press a key)\n" -// The string used in the I_Error alert upon trying to host through command line parameters. -// Generally less filled with newlines, since Windows gives you lots more room to work with. -#define UPDATE_ALERT_STRING_CONSOLE \ -"A new update is available for SRB2Kart.\n"\ -"Please visit mb.srb2.org to download it.\n"\ -"\n"\ -"You are using version: %s\n"\ -"The newest version is: %s\n"\ -"\n"\ -"This update is required for online play using the Master Server.\n"\ -"You will not be able to connect to the Master Server\n"\ -"until you update to the newest version of the game.\n" - // For future use, the codebase is the version of SRB2 that the modification is based on, // and should not be changed unless you have merged changes between versions of SRB2 // (such as 2.0.4 to 2.0.5, etc) into your working copy. @@ -683,4 +658,10 @@ extern const char *compdate, *comptime, *comprevision, *compbranch; /// MIDI support is really shitty -- we don't use it anyway, so lets throw it behind a define #define NO_MIDI +#if defined (HAVE_CURL) && ! defined (NONET) +#define MASTERSERVER +#else +#undef UPDATE_ALERT +#endif + #endif // __DOOMDEF__ diff --git a/src/g_game.c b/src/g_game.c index b15013ded..187b7af23 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -58,6 +58,10 @@ #include "k_grandprix.h" #include "doomstat.h" +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; @@ -471,6 +475,7 @@ player_t *seenplayer; // player we're aiming at right now // now automatically allocated in D_RegisterClientCommands // so that it doesn't have to be updated depending on the value of MAXPLAYERS char player_names[MAXPLAYERS][MAXPLAYERNAME+1]; +INT32 player_name_changes[MAXPLAYERS]; // Allocation for time and nights data void G_AllocMainRecordData(INT16 i) @@ -1957,6 +1962,11 @@ void G_Ticker(boolean run) if (camtoggledelay[i]) camtoggledelay[i]--; } + + if (gametic % NAMECHANGERATE == 0) + { + memset(player_name_changes, 0, sizeof player_name_changes); + } } } @@ -4744,6 +4754,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/g_game.h b/src/g_game.h index 13376cd20..8407171df 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -27,7 +27,8 @@ extern char customversionstring[32]; #ifdef SEENAMES extern player_t *seenplayer; #endif -extern char player_names[MAXPLAYERS][MAXPLAYERNAME+1]; +extern char player_names[MAXPLAYERS][MAXPLAYERNAME+1]; +extern INT32 player_name_changes[MAXPLAYERS]; extern player_t players[MAXPLAYERS]; extern boolean playeringame[MAXPLAYERS]; diff --git a/src/http-mserv.c b/src/http-mserv.c index 47c2b5b0f..27a4e23f1 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -11,10 +11,12 @@ /* Documentation available here. - + */ +#ifdef HAVE_CURL #include +#endif #include "doomdef.h" #include "d_clisrv.h" @@ -49,6 +51,8 @@ consvar_t cv_masterserver_token = { NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ }; +#ifdef MASTERSERVER + static int hms_started; static char *hms_api; @@ -137,7 +141,7 @@ HMS_connect (const char *format, ...) if (cv_masterserver_token.string[0]) { quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0); - token_length = ( sizeof "?token="-1 )+ strlen(quack_token); + token_length = ( sizeof "&token="-1 )+ strlen(quack_token); } else { @@ -152,7 +156,9 @@ HMS_connect (const char *format, ...) seek = strlen(hms_api) + 1;/* + '/' */ va_start (ap, format); - url = malloc(seek + vsnprintf(0, 0, format, ap) + token_length + 1); + url = malloc(seek + vsnprintf(0, 0, format, ap) + + sizeof "?v=2" - 1 + + token_length + 1); va_end (ap); sprintf(url, "%s/", hms_api); @@ -165,8 +171,11 @@ HMS_connect (const char *format, ...) seek += vsprintf(&url[seek], format, ap); va_end (ap); + strcpy(&url[seek], "?v=2"); + seek += sizeof "?v=2" - 1; + if (quack_token) - sprintf(&url[seek], "?token=%s", quack_token); + sprintf(&url[seek], "&token=%s", quack_token); CONS_Printf("HMS: connecting '%s'...\n", url); @@ -254,117 +263,6 @@ HMS_end (struct HMS_buffer *buffer) free(buffer); } -int -HMS_fetch_rooms (int joining, int query_id) -{ - struct HMS_buffer *hms; - int ok; - - int doing_shit; - - char *id; - char *title; - char *room_motd; - - int id_no; - - char *p; - char *end; - - int i; - - (void)query_id; - - hms = HMS_connect("rooms"); - - if (! hms) - return 0; - - if (HMS_do(hms)) - { - doing_shit = 1; - - p = hms->buffer; - - for (i = 0; i < NUM_LIST_ROOMS && ( end = strstr(p, "\n\n\n") );) - { - *end = '\0'; - - id = strtok(p, "\n"); - title = strtok(0, "\n"); - room_motd = strtok(0, ""); - - if (id && title && room_motd) - { - id_no = atoi(id); - - /* - Don't show the 'All' room if hosting. And it's a hack like this - because I'm way too lazy to add another feature to the MS. - */ - if (joining || id_no != 0) - { -#ifdef HAVE_THREADS - I_lock_mutex(&ms_QueryId_mutex); - { - if (query_id != ms_QueryId) - doing_shit = 0; - } - I_unlock_mutex(ms_QueryId_mutex); - - if (! doing_shit) - break; -#endif - - room_list[i].header.buffer[0] = 1; - - room_list[i].id = id_no; - strlcpy(room_list[i].name, title, sizeof room_list[i].name); - strlcpy(room_list[i].motd, room_motd, sizeof room_list[i].motd); - - i++; - } - - p = ( end + 3 );/* skip the three linefeeds */ - } - else - break; - } - - if (doing_shit) - room_list[i].header.buffer[0] = 0; - - ok = 1; - - if (doing_shit) - { -#ifdef HAVE_THREADS - I_lock_mutex(&m_menu_mutex); -#endif - { - for (i = 0; room_list[i].header.buffer[0]; i++) - { - if(*room_list[i].name != '\0') - { - MP_RoomMenu[i+1].text = room_list[i].name; - roomIds[i] = room_list[i].id; - MP_RoomMenu[i+1].status = IT_STRING|IT_CALL; - } - } - } -#ifdef HAVE_THREADS - I_unlock_mutex(m_menu_mutex); -#endif - } - } - else - ok = 0; - - HMS_end(hms); - - return ok; -} - int HMS_register (void) { @@ -373,28 +271,26 @@ HMS_register (void) char post[256]; - char *title; + char *contact; - hms = HMS_connect("rooms/%d/register", ms_RoomId); + hms = HMS_connect( + "games/%s/%d/servers/register", SRB2APPLICATION, MODVERSION); if (! hms) return 0; - title = curl_easy_escape(hms->curl, cv_servername.string, 0); + contact = curl_easy_escape(hms->curl, cv_server_contact.string, 0); snprintf(post, sizeof post, "port=%d&" - "title=%s&" - "version=%s", + "contact=%s", current_port, - title, - - SRB2VERSION + contact ); - curl_free(title); + curl_free(contact); curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); @@ -468,19 +364,13 @@ HMS_list_servers (void) { struct HMS_buffer *hms; - char *p; - - hms = HMS_connect("servers"); + hms = HMS_connect("games/%s/%d/servers", SRB2APPLICATION, MODVERSION); if (! hms) return; if (HMS_do(hms)) { - p = &hms->buffer[strlen(hms->buffer)]; - while (*--p == '\n') - ; - CONS_Printf("%s\n", hms->buffer); } @@ -488,35 +378,24 @@ HMS_list_servers (void) } msg_server_t * -HMS_fetch_servers (msg_server_t *list, int room_number, int query_id) +HMS_fetch_servers (msg_server_t *list, int query_id) { struct HMS_buffer *hms; int doing_shit; - char local_version[9]; - - char *room; - char *address; char *port; - char *title; - char *version; + char *contact; char *end; - char *section_end; char *p; int i; (void)query_id; - if (room_number > 0) - { - hms = HMS_connect("rooms/%d/servers", room_number); - } - else - hms = HMS_connect("servers"); + hms = HMS_connect("games/%s/%d/servers", SRB2APPLICATION, MODVERSION); if (! hms) return NULL; @@ -525,80 +404,51 @@ HMS_fetch_servers (msg_server_t *list, int room_number, int query_id) { doing_shit = 1; - snprintf(local_version, sizeof local_version, - "%s", - SRB2VERSION - ); - p = hms->buffer; i = 0; - do + while (i < MAXSERVERLIST && ( end = strchr(p, '\n') )) { - section_end = strstr(p, "\n\n"); + *end = '\0'; - room = strtok(p, "\n"); + address = strtok(p, " "); + port = strtok(0, " "); + contact = strtok(0, ""); - p = strtok(0, ""); - - if (! p) - break; - - while (i < MAXSERVERLIST && ( end = strchr(p, '\n') )) + if (address && port) { - *end = '\0'; - - address = strtok(p, " "); - port = strtok(0, " "); - title = strtok(0, " "); - version = strtok(0, ""); - - if (address && port && title && version) - { #ifdef HAVE_THREADS - I_lock_mutex(&ms_QueryId_mutex); - { - if (query_id != ms_QueryId) - doing_shit = 0; - } - I_unlock_mutex(ms_QueryId_mutex); + I_lock_mutex(&ms_QueryId_mutex); + { + if (query_id != ms_QueryId) + doing_shit = 0; + } + I_unlock_mutex(ms_QueryId_mutex); - if (! doing_shit) - break; + if (! doing_shit) + break; #endif - if (strcmp(version, local_version) == 0) - { - strlcpy(list[i].ip, address, sizeof list[i].ip); - strlcpy(list[i].port, port, sizeof list[i].port); - strlcpy(list[i].name, title, sizeof list[i].name); - strlcpy(list[i].version, version, sizeof list[i].version); + strlcpy(list[i].ip, address, sizeof list[i].ip); + strlcpy(list[i].port, port, sizeof list[i].port); - list[i].room = atoi(room); - - list[i].header.buffer[0] = 1; - - i++; - } - - if (end == section_end)/* end of list for this room */ - break; - else - p = ( end + 1 );/* skip server delimiter */ - } - else + if (contact) { - section_end = 0;/* malformed so quit the parsing */ - break; + strlcpy(list[i].contact, contact, sizeof list[i].contact); } + + list[i].header.buffer[0] = 1; + + i++; + + p = ( end + 1 );/* skip server delimiter */ } - - if (! doing_shit) + else + { + /* malformed so quit the parsing */ break; - - p = ( section_end + 2 ); + } } - while (section_end) ; if (doing_shit) list[i].header.buffer[0] = 0; @@ -620,7 +470,7 @@ HMS_compare_mod_version (char *buffer, size_t buffer_size) char *version; char *version_name; - hms = HMS_connect("versions/%d", MODID); + hms = HMS_connect("games/%s/version", SRB2APPLICATION); if (! hms) return 0; @@ -649,6 +499,19 @@ HMS_compare_mod_version (char *buffer, size_t buffer_size) return ok; } +static char * +Strip_trailing_slashes (char *api) +{ + char * p = &api[strlen(api)]; + + while (*--p == '/') + ; + + p[1] = '\0'; + + return api; +} + void HMS_set_api (char *api) { @@ -657,17 +520,21 @@ HMS_set_api (char *api) #endif { free(hms_api); - hms_api = api; + hms_api = Strip_trailing_slashes(api); } #ifdef HAVE_THREADS I_unlock_mutex(hms_api_mutex); #endif } +#endif/*MASTERSERVER*/ + static void MasterServer_Debug_OnChange (void) { +#ifdef MASTERSERVER /* TODO: change to 'latest-log.txt' for log files revision. */ if (cv_masterserver_debug.value) CONS_Printf("Master server debug messages will appear in log.txt\n"); +#endif } diff --git a/src/k_kart.c b/src/k_kart.c index 3dd61fa8c..4352ce5c5 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -306,35 +306,35 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = /*Jawz x2*/ { 0, 0, 1, 2, 0, 0, 0, 0 } // Jawz x2 }; -static INT32 K_KartItemOddsBattle[NUMKARTRESULTS-1][6] = +static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][2] = { - //P-Odds 0 1 2 3 4 5 - /*Sneaker*/ { 3, 2, 2, 2, 0, 2 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker - /*Invincibility*/ { 0, 1, 2, 3, 4, 2 }, // Invincibility - /*Banana*/ { 2, 1, 0, 0, 0, 0 }, // Banana - /*Eggman Monitor*/ { 1, 1, 0, 0, 0, 0 }, // Eggman Monitor - /*Orbinaut*/ { 6, 2, 1, 0, 0, 0 }, // Orbinaut - /*Jawz*/ { 3, 3, 3, 2, 0, 2 }, // Jawz - /*Mine*/ { 2, 3, 3, 1, 0, 2 }, // Mine - /*Ballhog*/ { 0, 1, 2, 1, 0, 2 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb - /*Grow*/ { 0, 0, 1, 2, 4, 2 }, // Grow - /*Shrink*/ { 0, 0, 0, 0, 0, 0 }, // Shrink - /*Thunder Shield*/ { 0, 0, 0, 0, 0, 0 }, // Thunder Shield - /*Bubble Shield*/ { 0, 0, 0, 0, 0, 0 }, // Bubble Shield - /*Flame Shield*/ { 0, 0, 0, 0, 0, 0 }, // Flame Shield - /*Hyudoro*/ { 1, 1, 0, 0, 0, 0 }, // Hyudoro - /*Pogo Spring*/ { 1, 1, 0, 0, 0, 0 }, // Pogo Spring - /*Super Ring*/ { 0, 0, 0, 0, 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - /*Sneaker x2*/ { 0, 0, 0, 0, 0, 0 }, // Sneaker x2 - /*Sneaker x3*/ { 0, 0, 0, 2, 4, 2 }, // Sneaker x3 - /*Banana x3*/ { 1, 2, 1, 0, 0, 0 }, // Banana x3 - /*Banana x10*/ { 0, 0, 1, 1, 0, 2 }, // Banana x10 - /*Orbinaut x3*/ { 0, 1, 2, 1, 0, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 0, 0, 1, 3, 4, 2 }, // Orbinaut x4 - /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 } // Jawz x2 + //P-Odds 0 1 + /*Sneaker*/ { 2, 1 }, // Sneaker + /*Rocket Sneaker*/ { 0, 0 }, // Rocket Sneaker + /*Invincibility*/ { 2, 1 }, // Invincibility + /*Banana*/ { 1, 0 }, // Banana + /*Eggman Monitor*/ { 1, 0 }, // Eggman Monitor + /*Orbinaut*/ { 8, 0 }, // Orbinaut + /*Jawz*/ { 8, 1 }, // Jawz + /*Mine*/ { 4, 1 }, // Mine + /*Ballhog*/ { 2, 1 }, // Ballhog + /*Self-Propelled Bomb*/ { 0, 0 }, // Self-Propelled Bomb + /*Grow*/ { 2, 1 }, // Grow + /*Shrink*/ { 0, 0 }, // Shrink + /*Thunder Shield*/ { 0, 0 }, // Thunder Shield + /*Bubble Shield*/ { 0, 0 }, // Bubble Shield + /*Flame Shield*/ { 0, 0 }, // Flame Shield + /*Hyudoro*/ { 2, 0 }, // Hyudoro + /*Pogo Spring*/ { 2, 0 }, // Pogo Spring + /*Super Ring*/ { 0, 0 }, // Super Ring + /*Kitchen Sink*/ { 0, 0 }, // Kitchen Sink + /*Sneaker x2*/ { 0, 0 }, // Sneaker x2 + /*Sneaker x3*/ { 0, 1 }, // Sneaker x3 + /*Banana x3*/ { 1, 0 }, // Banana x3 + /*Banana x10*/ { 0, 1 }, // Banana x10 + /*Orbinaut x3*/ { 2, 0 }, // Orbinaut x3 + /*Orbinaut x4*/ { 1, 1 }, // Orbinaut x4 + /*Jawz x2*/ { 2, 1 } // Jawz x2 }; #define DISTVAR (2048) // Magic number distance for use with item roulette tiers @@ -620,19 +620,20 @@ INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) { UINT8 i; - UINT8 n = 0; UINT8 useodds = 0; UINT8 disttable[14]; - UINT8 totallen = 0; UINT8 distlen = 0; boolean oddsvalid[8]; + // Unused now, oops :V + (void)bestbumper; + for (i = 0; i < 8; i++) { UINT8 j; boolean available = false; - if (gametype == GT_BATTLE && i > 5) + if (gametype == GT_BATTLE && i > 1) { oddsvalid[i] = false; break; @@ -653,30 +654,24 @@ UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbum #define SETUPDISTTABLE(odds, num) \ if (oddsvalid[odds]) \ for (i = num; i; --i) \ - disttable[distlen++] = odds; \ - totallen += num; + disttable[distlen++] = odds; if (gametype == GT_BATTLE) // Battle Mode { - SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,1); - SETUPDISTTABLE(4,1); - - if (player->kartstuff[k_roulettetype] == 1 && oddsvalid[5]) // 5 is the extreme odds of player-controlled "Karma" items - useodds = 5; + if (player->kartstuff[k_roulettetype] == 1 && oddsvalid[1] == true) + { + // 1 is the extreme odds of player-controlled "Karma" items + useodds = 1; + } else { - SINT8 wantedpos = (bestbumper-player->kartstuff[k_bumper]); // 0 is the best player's bumper count, 1 is a bumper below best, 2 is two bumpers below, etc - if (K_IsPlayerWanted(player)) - wantedpos++; - if (wantedpos > 4) // Don't run off into karma items - wantedpos = 4; - if (wantedpos < 0) // Don't go below somehow - wantedpos = 0; - n = (wantedpos * distlen) / totallen; - useodds = disttable[n]; + useodds = 0; + + if (oddsvalid[0] == false && oddsvalid[1] == true) + { + // try to use karma odds as a fallback + useodds = 1; + } } } else @@ -1240,7 +1235,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) } } -/** \brief Checks that the player is on an offroad subsector for realsies +/** \brief Checks that the player is on an offroad subsector for realsies. Also accounts for line riding to prevent cheese. \param mo player mobj object @@ -1248,18 +1243,64 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) */ static UINT8 K_CheckOffroadCollide(mobj_t *mo) { - UINT8 i; + // Check for sectors in touching_sectorlist + UINT8 i; // special type iter + msecnode_t *node; // touching_sectorlist iter + sector_t *s; // main sector shortcut + sector_t *s2; // FOF sector shortcut + ffloor_t *rover; // FOF + + fixed_t flr; + fixed_t cel; // floor & ceiling for height checks to make sure we're touching the offroad sector. I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); - for (i = 2; i < 5; i++) + for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next) { - if (P_MobjTouchingSectorSpecial(mo, 1, i, true)) - return i-1; - } + if (!node->m_sector) + break; // shouldn't happen. - return 0; + s = node->m_sector; + // 1: Check for the main sector, make sure we're on the floor of that sector and see if we can apply offroad. + // Make arbitrary Z checks because we want to check for 1 sector in particular, we don't want to affect the player if the offroad sector is way below them and they're lineriding a normal sector above. + + flr = P_MobjFloorZ(mo, s, s, mo->x, mo->y, NULL, false, true); + cel = P_MobjCeilingZ(mo, s, s, mo->x, mo->y, NULL, true, true); // get Z coords of both floors and ceilings for this sector (this accounts for slopes properly.) + // NOTE: we don't use P_GetZAt with our x/y directly because the mobj won't have the same height because of its hitbox on the slope. Complex garbage but tldr it doesn't work. + + if ( ((s->flags & SF_FLIPSPECIAL_FLOOR) && mo->z == flr) // floor check + || ((mo->eflags & MFE_VERTICALFLIP && (s->flags & SF_FLIPSPECIAL_CEILING) && (mo->z + mo->height) == cel)) ) // ceiling check. + + for (i = 2; i < 5; i++) // check for sector special + + if (GETSECSPECIAL(s->special, 1) == i) + return i-1; // return offroad type + + // 2: If we're here, we haven't found anything. So let's try looking for FOFs in the sectors using the same logic. + for (rover = s->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS)) // This FOF doesn't exist anymore. + continue; + + s2 = §ors[rover->secnum]; // makes things easier for us + + flr = P_GetFOFBottomZ(mo, s, rover, mo->x, mo->y, NULL); + cel = P_GetFOFTopZ(mo, s, rover, mo->x, mo->y, NULL); // Z coords for fof top/bottom. + + // we will do essentially the same checks as above instead of bothering with top/bottom height of the FOF. + // Reminder that an FOF's floor is its bottom, silly! + if ( ((s2->flags & SF_FLIPSPECIAL_FLOOR) && mo->z == cel) // "floor" check + || ((s2->flags & SF_FLIPSPECIAL_CEILING) && (mo->z + mo->height) == flr) ) // "ceiling" check. + + for (i = 2; i < 5; i++) // check for sector special + + if (GETSECSPECIAL(s2->special, 1) == i) + return i-1; // return offroad type + + } + } + return 0; // couldn't find any offroad } /** \brief Updates the Player's offroad value once per frame @@ -3680,13 +3721,13 @@ void K_PuntMine(mobj_t *thismine, mobj_t *punter) mine->flags2 = thismine->flags2; mine->floorz = thismine->floorz; mine->ceilingz = thismine->ceilingz; - + //Since we aren't using P_KillMobj, we need to clean up the hnext reference { P_SetTarget(&thismine->target->hnext, NULL); //target is the player who owns the mine thismine->target->player->kartstuff[k_bananadrag] = 0; thismine->target->player->kartstuff[k_itemheld] = 0; - + if (--thismine->target->player->kartstuff[k_itemamount] <= 0) thismine->target->player->kartstuff[k_itemtype] = KITEM_NONE; } @@ -4340,7 +4381,7 @@ void K_DropRocketSneaker(player_t *player) flingangle = -(ANG60); else flingangle = ANG60; - + S_StartSound(shoe, shoe->info->deathsound); P_SetObjectMomZ(shoe, 8*FRACUNIT, false); P_InstaThrust(shoe, R_PointToAngle2(shoe->target->x, shoe->target->y, shoe->x, shoe->y)+flingangle, 16*FRACUNIT); @@ -4944,14 +4985,22 @@ player_t *K_FindJawzTarget(mobj_t *actor, player_t *source) static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) { const INT32 numsnds = 13; + + const fixed_t closedist = 160*FRACUNIT; + const fixed_t fardist = 1536*FRACUNIT; + + const UINT8 dampenval = 48; // 255 * 48 = close enough to FRACUNIT/6 + INT32 class, s, w; // engine class number + UINT8 volume = 255; - fixed_t volumedampen = 0; + fixed_t volumedampen = FRACUNIT; + INT32 targetsnd = 0; INT32 i; - s = (player->kartspeed-1)/3; - w = (player->kartweight-1)/3; + s = (player->kartspeed - 1) / 3; + w = (player->kartweight - 1) / 3; #define LOCKSTAT(stat) \ if (stat < 0) { stat = 0; } \ @@ -4960,83 +5009,119 @@ static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) LOCKSTAT(w); #undef LOCKSTAT - class = s+(3*w); + class = s + (3*w); - // Silence the engines if (leveltime < 8 || player->spectator) { - player->karthud[khud_enginesnd] = 0; // Reset sound number + // Silence the engines, and reset sound number while we're at it. + player->karthud[khud_enginesnd] = 0; return; } #if 0 if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct! #else - if (leveltime % 8) // .25 seconds of wait time between engine sounds + if (leveltime % 8) #endif + { + // .25 seconds of wait time between each engine sound playback return; + } if (player->respawn.state == RESPAWNST_DROP) // Dropdashing + { + // Dropdashing targetsnd = ((cmd->buttons & BT_ACCELERATE) ? 12 : 0); - else if (K_PlayerEBrake(player) == true) // Spindashing + } + else if (K_PlayerEBrake(player) == true) + { + // Spindashing targetsnd = ((cmd->buttons & BT_DRIFT) ? 12 : 0); + } else - targetsnd = (((6*K_GetForwardMove(player))/25) + ((player->speed / mapobjectscale)/5))/2; + { + // Average out the value of forwardmove and the speed that you're moving at. + targetsnd = (((6 * cmd->forwardmove) / 25) + ((player->speed / mapobjectscale) / 5)) / 2; + } - if (targetsnd < 0) - targetsnd = 0; - if (targetsnd > 12) - targetsnd = 12; + if (targetsnd < 0) { targetsnd = 0; } + if (targetsnd > 12) { targetsnd = 12; } - if (player->karthud[khud_enginesnd] < targetsnd) - player->karthud[khud_enginesnd]++; - if (player->karthud[khud_enginesnd] > targetsnd) - player->karthud[khud_enginesnd]--; + if (player->karthud[khud_enginesnd] < targetsnd) { player->karthud[khud_enginesnd]++; } + if (player->karthud[khud_enginesnd] > targetsnd) { player->karthud[khud_enginesnd]--; } - if (player->karthud[khud_enginesnd] < 0) - player->karthud[khud_enginesnd] = 0; - if (player->karthud[khud_enginesnd] > 12) - player->karthud[khud_enginesnd] = 12; + if (player->karthud[khud_enginesnd] < 0) { player->karthud[khud_enginesnd] = 0; } + if (player->karthud[khud_enginesnd] > 12) { player->karthud[khud_enginesnd] = 12; } + + // This code calculates how many players (and thus, how many engine sounds) are within ear shot, + // and rebalances the volume of your engine sound based on how far away they are. + + // This results in multiple things: + // - When on your own, you will hear your own engine sound extremely clearly. + // - When you were alone but someone is gaining on you, yours will go quiet, and you can hear theirs more clearly. + // - When around tons of people, engine sounds will try to rebalance to not be as obnoxious. for (i = 0; i < MAXPLAYERS; i++) { UINT8 thisvol = 0; fixed_t dist; - if (!playeringame[i] || !players[i].mo || players[i].spectator || players[i].exiting) - continue; - - if (P_IsDisplayPlayer(&players[i])) + if (!playeringame[i] || !players[i].mo) { - volumedampen += FRACUNIT; // We already know what this is gonna be, let's not waste our time. + // This player doesn't exist. continue; } - dist = P_AproxDistance(P_AproxDistance(player->mo->x-players[i].mo->x, - player->mo->y-players[i].mo->y), player->mo->z-players[i].mo->z) / 2; + if (players[i].spectator) + { + // This player isn't playing an engine sound. + continue; + } + + if (P_IsDisplayPlayer(&players[i])) + { + // Don't dampen yourself! + continue; + } + + dist = P_AproxDistance( + P_AproxDistance( + player->mo->x - players[i].mo->x, + player->mo->y - players[i].mo->y), + player->mo->z - players[i].mo->z) / 2; dist = FixedDiv(dist, mapobjectscale); - if (dist > 1536< fardist) + { + // ENEMY OUT OF RANGE ! continue; - else if (dist < 160<>FRACBITS)) / (((1536<>(FRACBITS+4)); + { + thisvol = (15 * ((closedist - dist) / FRACUNIT)) / ((fardist - closedist) >> (FRACBITS+4)); + } - if (thisvol == 0) - continue; - - volumedampen += (thisvol * 257); // 255 * 257 = FRACUNIT + volumedampen += (thisvol * dampenval); } if (volumedampen > FRACUNIT) - volume = FixedDiv(volume<>FRACBITS; + { + volume = FixedDiv(volume * FRACUNIT, volumedampen) / FRACUNIT; + } - if (volume <= 0) // Might as well + if (volume <= 0) + { + // Don't need to play the sound at all. return; + } - S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->karthud[khud_enginesnd]) + (class*numsnds), volume); + S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->karthud[khud_enginesnd]) + (class * numsnds), volume); } static void K_UpdateInvincibilitySounds(player_t *player) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index a6005946f..9ddbd868e 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -3624,41 +3624,6 @@ static int lib_kGetItemPatch(lua_State *L) return 1; } -// sets the remaining time before players blow up -static int lib_kSetRaceCountdown(lua_State *L) -{ - tic_t c = (tic_t)luaL_checkinteger(L, 1); - racecountdown = c; - return 0; -} - -// sets the remaining time before the race ends after everyone finishes -static int lib_kSetExitCountdown(lua_State *L) -{ - tic_t c = (tic_t)luaL_checkinteger(L, 1); - NOHUD - exitcountdown = c; - return 0; -} - -// Sets the item cooldown before another shrink / SPB can be rolled -static int lib_kSetIndirectItemCountdown(lua_State *L) -{ - tic_t c = (tic_t)luaL_checkinteger(L, 1); - NOHUD - indirectitemcooldown = c; - return 0; -} - -// Sets the item cooldown before another shrink / SPB can be rolled -static int lib_kSetHyuCountdown(lua_State *L) -{ - tic_t c = (tic_t)luaL_checkinteger(L, 1); - NOHUD - hyubgone = c; - return 0; -} - static luaL_Reg lib[] = { {"print", lib_print}, {"chatprint", lib_chatprint}, @@ -3920,10 +3885,6 @@ static luaL_Reg lib[] = { {"K_GetKartAccel",lib_kGetKartAccel}, {"K_GetKartFlashing",lib_kGetKartFlashing}, {"K_GetItemPatch",lib_kGetItemPatch}, - {"K_SetRaceCountdown",lib_kSetRaceCountdown}, - {"K_SetExitCountdown",lib_kSetExitCountdown}, - {"K_SetIndirectItemCooldown",lib_kSetIndirectItemCountdown}, - {"K_SetHyudoroCooldown",lib_kSetHyuCountdown}, {NULL, NULL} }; diff --git a/src/lua_script.c b/src/lua_script.c index b709bc0e7..c8d518d66 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -370,7 +370,11 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"exitcountdown")) { lua_pushinteger(L, exitcountdown); // This name is pretty dumb. Hence why we'll prefer more descriptive names at least in Lua... return 1; + } else if (fastcmp(word,"replayplayback")) { + lua_pushboolean(L, demo.playback); + return 1; } + return 0; } @@ -381,6 +385,15 @@ int LUA_WriteGlobals(lua_State *L, const char *word) redscore = (UINT32)luaL_checkinteger(L, 2); else if (fastcmp(word, "bluescore")) bluescore = (UINT32)luaL_checkinteger(L, 2); + // SRB2Kart + else if (fastcmp(word,"racecountdown")) + racecountdown = (tic_t)luaL_checkinteger(L, 2); + else if (fastcmp(word,"exitcountdown")) + exitcountdown = (tic_t)luaL_checkinteger(L, 2); + else if (fastcmp(word,"indirectitemcooldown")) + indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2); + else if (fastcmp(word,"hyubgone")) + hyubgone = (tic_t)luaL_checkinteger(L, 2); else return 0; diff --git a/src/m_menu.c b/src/m_menu.c index 1330469ce..62225330d 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -85,6 +85,11 @@ int snprintf(char *str, size_t n, const char *fmt, ...); //int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); #endif +#ifdef HAVE_DISCORDRPC +//#include "discord_rpc.h" +#include "discord.h" +#endif + #define SKULLXOFF -32 #define LINEHEIGHT 16 #define STRINGHEIGHT 8 @@ -186,7 +191,6 @@ static void M_StopMessage(INT32 choice); #ifndef NONET static void M_HandleServerPage(INT32 choice); -static void M_RoomMenu(INT32 choice); #endif // Prototyping is fun, innit? @@ -194,9 +198,18 @@ static void M_RoomMenu(INT32 choice); // NEEDED FUNCTION PROTOTYPES GO HERE // ========================================================================== +void M_SetWaitingMode(int mode); +int M_GetWaitingMode(void); + // the haxor message menu menu_t MessageDef; +#ifdef HAVE_DISCORDRPC +menu_t MISC_DiscordRequestsDef; +static void M_HandleDiscordRequests(INT32 choice); +static void M_DrawDiscordRequests(void); +#endif + menu_t SPauseDef; #define lsheadingheight 16 @@ -263,7 +276,6 @@ static void M_ConnectMenu(INT32 choice); static void M_ConnectMenuModChecks(INT32 choice); static void M_Refresh(INT32 choice); static void M_Connect(INT32 choice); -static void M_ChooseRoom(INT32 choice); #endif #ifndef TESTERS static void M_StartOfflineServerMenu(INT32 choice); @@ -306,6 +318,9 @@ menu_t OP_SoundOptionsDef; //Misc menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef; +#ifdef HAVE_DISCORDRPC +menu_t OP_DiscordOptionsDef; +#endif menu_t OP_HUDOptionsDef, OP_ChatOptionsDef; menu_t OP_GameOptionsDef, OP_ServerOptionsDef; #ifndef NONET @@ -368,7 +383,6 @@ static void M_DrawMonitorToggles(void); static void M_DrawMPMainMenu(void); #ifndef NONET static void M_DrawConnectMenu(void); -static void M_DrawRoomMenu(void); #endif static void M_DrawJoystick(void); static void M_DrawSetupMultiPlayerMenu(void); @@ -614,6 +628,10 @@ static menuitem_t MPauseMenu[] = {IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16}, {IT_STRING | IT_CALL, NULL, "Switch Map..." , M_MapChange, 24}, +#ifdef HAVE_DISCORDRPC + {IT_STRING | IT_SUBMENU, NULL, "Ask To Join Requests...", &MISC_DiscordRequestsDef, 24}, +#endif + {IT_CALL | IT_STRING, NULL, "Continue", M_SelectableClearMenus, 40}, {IT_CALL | IT_STRING, NULL, "P1 Setup...", M_SetupMultiPlayer, 48}, // splitscreen {IT_CALL | IT_STRING, NULL, "P2 Setup...", M_SetupMultiPlayer2, 56}, // splitscreen @@ -637,6 +655,9 @@ typedef enum mpause_addons = 0, mpause_scramble, mpause_switchmap, +#ifdef HAVE_DISCORDRPC + mpause_discordrequests, +#endif mpause_continue, mpause_psetupsplit, @@ -687,6 +708,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 // ----------------- @@ -1020,7 +1048,7 @@ static menuitem_t MP_MainMenu[] = static menuitem_t MP_ServerMenu[] = { {IT_STRING|IT_CVAR, NULL, "Max. Player Count", &cv_maxplayers, 10}, - {IT_STRING|IT_CALL, NULL, "Room...", M_RoomMenu, 20}, + {IT_STRING|IT_CVAR, NULL, "Advertise", &cv_advertise, 20}, {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name", &cv_servername, 30}, {IT_STRING|IT_CVAR, NULL, "Game Type", &cv_newgametype, 68}, @@ -1051,54 +1079,30 @@ static menuitem_t MP_PlayerSetupMenu[] = #ifndef NONET static menuitem_t MP_ConnectMenu[] = { - {IT_STRING | IT_CALL, NULL, "Room...", M_RoomMenu, 4}, - {IT_STRING | IT_CVAR, NULL, "Sort By", &cv_serversort, 12}, - {IT_STRING | IT_KEYHANDLER, NULL, "Page", M_HandleServerPage, 20}, - {IT_STRING | IT_CALL, NULL, "Refresh", M_Refresh, 28}, + {IT_STRING | IT_CVAR, NULL, "Sort By", &cv_serversort, 4}, + {IT_STRING | IT_KEYHANDLER, NULL, "Page", M_HandleServerPage, 12}, + {IT_STRING | IT_CALL, NULL, "Refresh", M_Refresh, 20}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 48-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 60-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 72-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 84-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 96-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 108-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 120-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 132-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 144-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 156-4}, - {IT_STRING | IT_SPACE, NULL, "", M_Connect, 168-4}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 36}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 48}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 60}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 72}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 84}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 96}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 108}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 120}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 132}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 144}, + {IT_STRING | IT_SPACE, NULL, "", M_Connect, 156}, }; enum { - mp_connect_room, mp_connect_sort, mp_connect_page, mp_connect_refresh, FIRSTSERVERLINE }; - -menuitem_t MP_RoomMenu[] = -{ - {IT_STRING | IT_CALL, NULL, "", M_ChooseRoom, 9}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 18}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 27}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 36}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 45}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 54}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 63}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 72}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 81}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 90}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 99}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 108}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 117}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 126}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 135}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 144}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 153}, - {IT_DISABLED, NULL, "", M_ChooseRoom, 162}, -}; #endif // ------------------------------------ @@ -1344,11 +1348,17 @@ static menuitem_t OP_SoundOptionsMenu[] = static menuitem_t OP_DataOptionsMenu[] = { + {IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 10}, {IT_STRING | IT_CALL, NULL, "Addon Options...", M_AddonsOptions, 20}, {IT_STRING | IT_SUBMENU, NULL, "Replay Options...", &MISC_ReplayOptionsDef, 30}, +#ifdef HAVE_DISCORDRPC + {IT_STRING | IT_SUBMENU, NULL, "Discord Options...", &OP_DiscordOptionsDef, 40}, + {IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 60}, +#else {IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 50}, +#endif }; static menuitem_t OP_ScreenshotOptionsMenu[] = @@ -1397,7 +1407,7 @@ static menuitem_t OP_AddonsOptionsMenu[] = {IT_HEADER, NULL, "Menu", NULL, 0}, {IT_STRING|IT_CVAR, NULL, "Location", &cv_addons_option, 10}, {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_addons_folder, 20}, - {IT_STRING|IT_CVAR, NULL, "Identify addons via", &cv_addons_md5, 48}, + {IT_STRING|IT_CVAR, NULL, "Identify addons via", &cv_addons_md5, 48}, {IT_STRING|IT_CVAR, NULL, "Show unsupported file types", &cv_addons_showall, 58}, {IT_HEADER, NULL, "Search", NULL, 76}, @@ -1410,6 +1420,19 @@ enum op_addons_folder = 2, }; +#ifdef HAVE_DISCORDRPC +static menuitem_t OP_DiscordOptionsMenu[] = +{ + {IT_STRING | IT_CVAR, NULL, "Rich Presence", &cv_discordrp, 10}, + + {IT_HEADER, NULL, "Rich Presence Settings", NULL, 30}, + {IT_STRING | IT_CVAR, NULL, "Streamer Mode", &cv_discordstreamer, 40}, + + {IT_STRING | IT_CVAR, NULL, "Allow Ask To Join", &cv_discordasks, 60}, + {IT_STRING | IT_CVAR, NULL, "Allow Invites", &cv_discordinvites, 70}, +}; +#endif + static menuitem_t OP_HUDOptionsMenu[] = { @@ -1658,6 +1681,20 @@ menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72); menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72); menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72); +#ifdef HAVE_DISCORDRPC +menu_t MISC_DiscordRequestsDef = { + MN_NONE, + NULL, + sizeof (MISC_DiscordRequestsMenu)/sizeof (menuitem_t), + &MPauseDef, + MISC_DiscordRequestsMenu, + M_DrawDiscordRequests, + 0, 0, + 0, + NULL +}; +#endif + // Misc Main Menu menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(MN_NONE, NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40); menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(MN_NONE, NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40); @@ -1909,18 +1946,6 @@ menu_t MP_ConnectDef = 0, M_CancelConnect }; -menu_t MP_RoomDef = -{ - MN_NONE, - "M_MULTI", - sizeof (MP_RoomMenu)/sizeof (menuitem_t), - &MP_ConnectDef, - MP_RoomMenu, - M_DrawRoomMenu, - 27, 32, - 0, - NULL -}; #endif menu_t MP_PlayerSetupDef = { @@ -2050,6 +2075,9 @@ menu_t OP_OpenGLOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_VIDEO", OP_OpenGLOptio menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30); menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30); menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE(MN_NONE, "M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30); +#ifdef HAVE_DISCORDRPC +menu_t OP_DiscordOptionsDef = DEFAULTMENUSTYLE(MN_NONE, NULL, OP_DiscordOptionsMenu, &OP_DataOptionsDef, 30, 30); +#endif menu_t OP_EraseDataDef = DEFAULTMENUSTYLE(MN_NONE, "M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 30, 30); // ========================================================================== @@ -3262,12 +3290,18 @@ void M_StartControlPanel(void) MPauseMenu[mpause_psetup].status = IT_DISABLED; MISC_ChangeTeamMenu[0].status = IT_DISABLED; MISC_ChangeSpectateMenu[0].status = IT_DISABLED; + // Reset these in case splitscreen messes things up + MPauseMenu[mpause_addons].alphaKey = 8; + MPauseMenu[mpause_scramble].alphaKey = 8; + MPauseMenu[mpause_switchmap].alphaKey = 24; + MPauseMenu[mpause_switchteam].alphaKey = 48; MPauseMenu[mpause_switchspectate].alphaKey = 48; MPauseMenu[mpause_options].alphaKey = 64; MPauseMenu[mpause_title].alphaKey = 80; MPauseMenu[mpause_quit].alphaKey = 88; + Dummymenuplayer_OnChange(); if ((server || IsPlayerAdmin(consoleplayer))) @@ -3339,6 +3373,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; } @@ -3379,30 +3426,6 @@ void M_SetupNextMenu(menu_t *menudef) { INT16 i; -#ifdef HAVE_THREADS - if (currentMenu == &MP_RoomDef || currentMenu == &MP_ConnectDef) - { - I_lock_mutex(&ms_QueryId_mutex); - { - ms_QueryId++; - } - I_unlock_mutex(ms_QueryId_mutex); - } - - if (currentMenu == &MP_ConnectDef) - { - I_lock_mutex(&ms_ServerList_mutex); - { - if (ms_ServerList) - { - free(ms_ServerList); - ms_ServerList = NULL; - } - } - I_unlock_mutex(ms_ServerList_mutex); - } -#endif/*HAVE_THREADS*/ - if (currentMenu->quitroutine) { // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH @@ -3471,7 +3494,7 @@ void M_Ticker(void) setmodeneeded = vidm_previousmode + 1; } -#ifdef HAVE_THREADS +#if defined (MASTERSERVER) && defined (HAVE_THREADS) I_lock_mutex(&ms_ServerList_mutex); { if (ms_ServerList) @@ -4176,6 +4199,25 @@ static void M_DrawPauseMenu(void) } #endif +#ifdef HAVE_DISCORDRPC + // kind of hackily baked in here + if (currentMenu == &MPauseDef && discordRequestList != NULL) + { + const tic_t freq = TICRATE/2; + + if ((leveltime % freq) >= freq/2) + { + V_DrawFixedPatch(204 * FRACUNIT, + (currentMenu->y + MPauseMenu[mpause_discordrequests].alphaKey - 1) * FRACUNIT, + FRACUNIT, + 0, + W_CachePatchName("K_REQUE2", PU_CACHE), + NULL + ); + } + } +#endif + M_DrawGenericMenu(); } @@ -6307,7 +6349,12 @@ static void M_Options(INT32 choice) OP_MainMenu[4].status = OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); OP_MainMenu[8].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); // Play credits + +#ifdef HAVE_DISCORDRPC + OP_DataOptionsMenu[4].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data +#else OP_DataOptionsMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data +#endif OP_GameOptionsMenu[3].status = (M_SecretUnlocked(SECRET_ENCORE)) ? (IT_CVAR|IT_STRING) : IT_SECRET; // cv_kartencore @@ -6348,6 +6395,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 // ====== @@ -7542,7 +7603,7 @@ static void M_DrawStatsMaps(int location) else V_DrawString(20, y, 0, va("%s %s %d", mapheaderinfo[mnum]->lvlttl, - (mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "ZONE"), + (mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "Zone"), mapheaderinfo[mnum]->actnum)); y += 8; @@ -8319,7 +8380,118 @@ static void M_EndGame(INT32 choice) // Connect Menu //=========================================================================== -#define SERVERHEADERHEIGHT 44 +void +M_SetWaitingMode (int mode) +{ +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + { + m_waiting_mode = mode; + } +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif +} + +int +M_GetWaitingMode (void) +{ + int mode; + +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + { + mode = m_waiting_mode; + } +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + + return mode; +} + +#ifdef MASTERSERVER +#ifdef HAVE_THREADS +static void +Spawn_masterserver_thread (const char *name, void (*thread)(int*)) +{ + int *id = malloc(sizeof *id); + + I_lock_mutex(&ms_QueryId_mutex); + { + *id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + I_spawn_thread(name, (I_thread_fn)thread, id); +} + +static int +Same_instance (int id) +{ + int okay; + + I_lock_mutex(&ms_QueryId_mutex); + { + okay = ( id == ms_QueryId ); + } + I_unlock_mutex(ms_QueryId_mutex); + + return okay; +} +#endif/*HAVE_THREADS*/ + +static void +Fetch_servers_thread (int *id) +{ + msg_server_t * server_list; + + (void)id; + + M_SetWaitingMode(M_WAITING_SERVERS); + +#ifdef HAVE_THREADS + server_list = GetShortServersList(*id); +#else + server_list = GetShortServersList(0); +#endif + + if (server_list) + { +#ifdef HAVE_THREADS + if (Same_instance(*id)) +#endif + { + M_SetWaitingMode(M_NOT_WAITING); + +#ifdef HAVE_THREADS + I_lock_mutex(&ms_ServerList_mutex); + { + ms_ServerList = server_list; + } + I_unlock_mutex(ms_ServerList_mutex); +#else + CL_QueryServerList(server_list); + free(server_list); +#endif + } +#ifdef HAVE_THREADS + else + { + free(server_list); + } +#endif + } + +#ifdef HAVE_THREADS + free(id); +#endif +} +#endif/*MASTERSERVER*/ + +#define SERVERHEADERHEIGHT 36 #define SERVERLINEHEIGHT 12 #define S_LINEY(n) currentMenu->y + SERVERHEADERHEIGHT + (n * SERVERLINEHEIGHT) @@ -8391,77 +8563,18 @@ static void M_Refresh(INT32 choice) if (rendermode == render_soft) I_FinishUpdate(); // page flip or blit buffer - // note: this is the one case where 0 is a valid room number - // because it corresponds to "All" - CL_UpdateServerList(!(ms_RoomId < 0), ms_RoomId); - // first page of servers serverlistpage = 0; -} -static INT32 menuRoomIndex = 0; - -static void M_DrawRoomMenu(void) -{ - static int frame = -12; - int dot_frame; - char text[4]; - - const char *rmotd; - const char *waiting_message; - - int dots; - - if (m_waiting_mode) - { - dot_frame = frame / 4; - dots = dot_frame + 3; - - strcpy(text, " "); - - if (dots > 0) - { - if (dot_frame < 0) - dot_frame = 0; - - strncpy(&text[dot_frame], "...", min(dots, 3 - dot_frame)); - } - - if (++frame == 12) - frame = -12; - - currentMenu->menuitems[0].text = text; - } - - // use generic drawer for cursor, items and title - M_DrawGenericMenu(); - - V_DrawString(currentMenu->x - 16, currentMenu->y, highlightflags, M_GetText("Select a room")); - - if (m_waiting_mode == M_NOT_WAITING) - { - M_DrawTextBox(144, 24, 20, 20); - - if (itemOn == 0) - rmotd = M_GetText("Don't connect to the Master Server."); - else - rmotd = room_list[itemOn-1].motd; - - rmotd = V_WordWrap(0, 20*8, 0, rmotd); - V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd); - } - - if (m_waiting_mode) - { - // Display a little "please wait" message. - M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); - if (m_waiting_mode == M_WAITING_VERSION) - waiting_message = "Checking for updates..."; - else - waiting_message = "Fetching room info..."; - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, waiting_message); - V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); - } +#ifdef MASTERSERVER +#ifdef HAVE_THREADS + Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread); +#else/*HAVE_THREADS*/ + Fetch_servers_thread(NULL); +#endif/*HAVE_THREADS*/ +#else/*MASTERSERVER*/ + CL_UpdateServerList(); +#endif/*MASTERSERVER*/ } static void M_DrawConnectMenu(void) @@ -8471,6 +8584,7 @@ static void M_DrawConnectMenu(void) //const char *spd = ""; const char *pwr = "----"; INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE; + int waiting; for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++) MP_ConnectMenu[i].status = IT_STRING | IT_SPACE; @@ -8478,20 +8592,12 @@ static void M_DrawConnectMenu(void) if (!numPages) numPages = 1; - // Room name - if (ms_RoomId < 0) - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_room].alphaKey, - highlightflags, (itemOn == mp_connect_room) ? "" : ""); - else - V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey, - highlightflags, room_list[menuRoomIndex].name); -#undef mp_server_room - } -#endif } static void M_MapChange(INT32 choice) @@ -9084,7 +9090,6 @@ static void M_StartServerMenu(INT32 choice) (void)choice; levellistmode = LLM_CREATESERVER; M_PrepareLevelSelect(); - ms_RoomId = -1; M_SetupNextMenu(&MP_ServerDef); } @@ -11466,3 +11471,161 @@ 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 isSmall) +{ + patch_t *stickerEnd; + INT32 height; + + if (isSmall == true) + { + stickerEnd = W_CachePatchName("K_STIKE2", PU_CACHE); + height = 6; + } + else + { + stickerEnd = W_CachePatchName("K_STIKEN", PU_CACHE); + height = 11; + } + + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, flags, stickerEnd, NULL); + V_DrawFill(x, y, width, height, 24|flags); + V_DrawFixedPatch((x + width)*FRACUNIT, y*FRACUNIT, FRACUNIT, flags|V_FLIP, stickerEnd, NULL); +} + +static void M_DrawDiscordRequests(void) +{ + discordRequest_t *curRequest = discordRequestList; + UINT8 *colormap; + patch_t *hand = NULL; + boolean removeRequest = false; + + const char *wantText = "...would like to join!"; + const char *controlText = "\x82" "ENTER" "\x80" " - Accept " "\x82" "ESC" "\x80" " - Decline"; + + INT32 x = 100; + INT32 y = 133; + + INT32 slide = 0; + INT32 maxYSlide = 18; + + if (confirmDelay > 0) + { + if (confirmAccept == true) + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_MENUCACHE); + hand = W_CachePatchName("K_LAPH02", PU_CACHE); + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE); + hand = W_CachePatchName("K_LAPH03", PU_CACHE); + } + + slide = confirmLength - confirmDelay; + + confirmDelay--; + + if (confirmDelay == 0) + removeRequest = true; + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREY, GTC_MENUCACHE); + } + + V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_LAPE01", PU_CACHE), colormap); + + if (hand != NULL) + { + fixed_t handoffset = (4 - abs((signed)(skullAnimCounter - 4))) * FRACUNIT; + V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT + handoffset, FRACUNIT, 0, hand, NULL); + } + + M_DrawSticker(x + (slide * 32), y - 1, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false); + V_DrawThinString(x + (slide * 32), y, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_YELLOWMAP, M_GetDiscordName(curRequest)); + + M_DrawSticker(x, y + 12, V_ThinStringWidth(wantText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true); + V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE|V_6WIDTHSPACE, wantText); + + M_DrawSticker(x, y + 26, V_ThinStringWidth(controlText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true); + V_DrawThinString(x, y + 24, V_ALLOWLOWERCASE|V_6WIDTHSPACE, controlText); + + y -= 18; + + while (curRequest->next != NULL) + { + INT32 ySlide = min(slide * 4, maxYSlide); + + curRequest = curRequest->next; + + M_DrawSticker(x, y - 1 + ySlide, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false); + V_DrawThinString(x, y + ySlide, V_ALLOWLOWERCASE|V_6WIDTHSPACE, M_GetDiscordName(curRequest)); + + y -= 12; + maxYSlide = 12; + } + + if (removeRequest == true) + { + DRPC_RemoveRequest(discordRequestList); + + if (discordRequestList == NULL) + { + // No other requests + MPauseMenu[mpause_discordrequests].status = IT_GRAYEDOUT; + + if (currentMenu->prevMenu) + { + M_SetupNextMenu(currentMenu->prevMenu); + if (currentMenu == &MPauseDef) + itemOn = mpause_continue; + } + else + M_ClearMenus(true); + + return; + } + } +} +#endif diff --git a/src/m_menu.h b/src/m_menu.h index d735a7fcf..66ef5c8d0 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -239,7 +239,6 @@ typedef enum M_NOT_WAITING, M_WAITING_VERSION, - M_WAITING_ROOMS, M_WAITING_SERVERS, } M_waiting_mode_t; @@ -336,12 +335,6 @@ typedef struct menuitem_s UINT8 alphaKey; } menuitem_t; -extern menuitem_t MP_RoomMenu[]; -extern UINT32 roomIds[NUM_LIST_ROOMS]; - -extern menuitem_t MP_RoomMenu[]; -extern UINT32 roomIds[NUM_LIST_ROOMS]; - typedef struct menu_s { UINT32 menuid; // ID to encode menu type and hierarchy @@ -478,6 +471,8 @@ void Addons_option_Onchange(void); void M_ReplayHut(INT32 choice); void M_SetPlaybackMenuPointer(void); +void M_RefreshPauseMenu(void); + INT32 HU_GetHighlightColor(void); // Moviemode menu updating diff --git a/src/mserv.c b/src/mserv.c index 44fa8e3ae..a686c6e2f 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -23,6 +23,12 @@ #include "m_menu.h" #include "z_zone.h" +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + +#ifdef MASTERSERVER + static int MSId; static int MSRegisteredId = -1; @@ -43,27 +49,33 @@ static I_cond MSCond; # define Unlock_state() #endif/*HAVE_THREADS*/ -static void Update_parameters (void); - #ifndef NONET static void Command_Listserv_f(void); #endif + +#endif/*MASTERSERVER*/ + +static void Update_parameters (void); + static void MasterServer_OnChange(void); +static void Advertise_OnChange(void); + static CV_PossibleValue_t masterserver_update_rate_cons_t[] = { {2, "MIN"}, {60, "MAX"}, - {0} + {0, NULL} }; -consvar_t cv_masterserver = {"masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_masterserver = {"masterserver", "https://ms.kartkrew.org/ms/api", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_server_contact = {"server_contact", "", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_masterserver_update_rate = {"masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_masterserver_update_rate = {"masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, MasterClient_Ticker, 0, NULL, NULL, 0, 0, NULL}; -INT16 ms_RoomId = -1; +consvar_t cv_advertise = {"advertise", "No", CV_NETVAR|CV_CALL|CV_NOINIT, CV_YesNo, Advertise_OnChange, 0, NULL, NULL, 0, 0, NULL}; -#ifdef HAVE_THREADS +#if defined (MASTERSERVER) && defined (HAVE_THREADS) int ms_QueryId; I_mutex ms_QueryId_mutex; @@ -73,10 +85,6 @@ I_mutex ms_ServerList_mutex; UINT16 current_port = 0; -// Room list is an external variable now. -// Avoiding having to get info ten thousand times... -msg_rooms_t room_list[NUM_LIST_ROOMS+1]; // +1 for easy test - /** Adds variables and commands relating to the master server. * * \sa cv_masterserver, cv_servername, @@ -90,11 +98,17 @@ void AddMServCommands(void) CV_RegisterVar(&cv_masterserver_timeout); CV_RegisterVar(&cv_masterserver_debug); CV_RegisterVar(&cv_masterserver_token); + CV_RegisterVar(&cv_advertise); CV_RegisterVar(&cv_servername); + CV_RegisterVar(&cv_server_contact); +#ifdef MASTERSERVER COM_AddCommand("listserv", Command_Listserv_f); #endif +#endif } +#ifdef MASTERSERVER + static void WarnGUI (void) { #ifdef HAVE_THREADS @@ -107,14 +121,14 @@ static void WarnGUI (void) } #define NUM_LIST_SERVER MAXSERVERLIST -msg_server_t *GetShortServersList(INT32 room, int id) +msg_server_t *GetShortServersList(int id) { msg_server_t *server_list; // +1 for easy test server_list = malloc(( NUM_LIST_SERVER + 1 ) * sizeof *server_list); - if (HMS_fetch_servers(server_list, room, id)) + if (HMS_fetch_servers(server_list, id)) return server_list; else { @@ -124,17 +138,6 @@ msg_server_t *GetShortServersList(INT32 room, int id) } } -INT32 GetRoomsList(boolean hosting, int id) -{ - if (HMS_fetch_rooms( ! hosting, id)) - return 1; - else - { - WarnGUI(); - return -1; - } -} - #ifdef UPDATE_ALERT char *GetMODVersion(int id) { @@ -168,15 +171,6 @@ char *GetMODVersion(int id) return NULL; } } - -// Console only version of the above (used before game init) -void GetMODVersion_Console(void) -{ - char buffer[16]; - - if (HMS_compare_mod_version(buffer, sizeof buffer) > 0) - I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, buffer); -} #endif #ifndef NONET @@ -267,6 +261,9 @@ Finish_unlist (void) Lock_state(); { registered = MSRegistered; + + if (MSId == MSRegisteredId) + MSId++; } Unlock_state(); @@ -287,13 +284,6 @@ Finish_unlist (void) I_wake_all_cond(&MSCond); #endif } - - Lock_state(); - { - if (MSId == MSRegisteredId) - MSId++; - } - Unlock_state(); } #ifdef HAVE_THREADS @@ -395,6 +385,7 @@ Change_masterserver_thread (char *api) void RegisterServer(void) { +#ifdef MASTERSERVER #ifdef HAVE_THREADS I_spawn_thread( "register-server", @@ -404,6 +395,7 @@ void RegisterServer(void) #else Finish_registration(); #endif +#endif/*MASTERSERVER*/ } static void UpdateServer(void) @@ -421,6 +413,7 @@ static void UpdateServer(void) void UnregisterServer(void) { +#ifdef MASTERSERVER #ifdef HAVE_THREADS I_spawn_thread( "unlist-server", @@ -430,12 +423,13 @@ void UnregisterServer(void) #else Finish_unlist(); #endif +#endif/*MASTERSERVER*/ } static boolean Online (void) { - return ( serverrunning && ms_RoomId > 0 ); + return ( serverrunning && cv_advertise.value ); } static inline void SendPingToMasterServer(void) @@ -465,9 +459,33 @@ static inline void SendPingToMasterServer(void) } } +void MasterClient_Ticker(void) +{ +#ifdef MASTERSERVER + SendPingToMasterServer(); +#endif +} + +static void +Set_api (const char *api) +{ +#ifdef HAVE_THREADS + I_spawn_thread( + "change-masterserver", + (I_thread_fn)Change_masterserver_thread, + strdup(api) + ); +#else + HMS_set_api(strdup(api)); +#endif +} + +#endif/*MASTERSERVER*/ + static void Update_parameters (void) { +#ifdef MASTERSERVER int registered; int delayed; @@ -487,29 +505,12 @@ Update_parameters (void) if (! delayed && registered) UpdateServer(); } -} - -void MasterClient_Ticker(void) -{ - SendPingToMasterServer(); -} - -static void -Set_api (const char *api) -{ -#ifdef HAVE_THREADS - I_spawn_thread( - "change-masterserver", - (I_thread_fn)Change_masterserver_thread, - strdup(api) - ); -#else - HMS_set_api(strdup(api)); -#endif +#endif/*MASTERSERVER*/ } static void MasterServer_OnChange(void) { +#ifdef MASTERSERVER UnregisterServer(); /* @@ -527,4 +528,36 @@ static void MasterServer_OnChange(void) if (Online()) RegisterServer(); +#endif/*MASTERSERVER*/ +} + +static void +Advertise_OnChange(void) +{ + int different; + + if (cv_advertise.value) + { + if (serverrunning) + { + Lock_state(); + { + different = ( MSId != MSRegisteredId ); + } + Unlock_state(); + + if (different) + { + RegisterServer(); + } + } + } + else + { + UnregisterServer(); + } + +#ifdef HAVE_DISCORDRPC + DRPC_UpdatePresence(); +#endif } diff --git a/src/mserv.h b/src/mserv.h index d0d5e49df..2a0afd1b3 100644 --- a/src/mserv.h +++ b/src/mserv.h @@ -16,9 +16,6 @@ #include "i_threads.h" -// lowered from 32 due to menu changes -#define NUM_LIST_ROOMS 16 - #if defined(_MSC_VER) #pragma pack(1) #endif @@ -35,19 +32,10 @@ typedef struct msg_header_t header; char ip[16]; char port[8]; - char name[32]; - INT32 room; + char contact[32]; char version[8]; // format is: x.yy.z (like 1.30.2 or 1.31) } ATTRPACK msg_server_t; -typedef struct -{ - msg_header_t header; - INT32 id; - char name[32]; - char motd[255]; -} ATTRPACK msg_rooms_t; - typedef struct { msg_header_t header; @@ -65,15 +53,13 @@ typedef struct // ================================ GLOBALS =============================== extern consvar_t cv_masterserver, cv_servername; +extern consvar_t cv_server_contact; extern consvar_t cv_masterserver_update_rate; extern consvar_t cv_masterserver_timeout; extern consvar_t cv_masterserver_debug; extern consvar_t cv_masterserver_token; -// < 0 to not connect (usually -1) (offline mode) -// == 0 to show all rooms, not a valid hosting room -// anything else is whatever room the MS assigns to that number (online mode) -extern INT16 ms_RoomId; +extern consvar_t cv_advertise; #ifdef HAVE_THREADS extern int ms_QueryId; @@ -88,24 +74,20 @@ void UnregisterServer(void); void MasterClient_Ticker(void); -msg_server_t *GetShortServersList(INT32 room, int id); -INT32 GetRoomsList(boolean hosting, int id); +msg_server_t *GetShortServersList(int id); #ifdef UPDATE_ALERT char *GetMODVersion(int id); -void GetMODVersion_Console(void); #endif -extern msg_rooms_t room_list[NUM_LIST_ROOMS+1]; void AddMServCommands(void); /* HTTP */ void HMS_set_api (char *api); -int HMS_fetch_rooms (int joining, int id); int HMS_register (void); int HMS_unlist (void); int HMS_update (void); void HMS_list_servers (void); -msg_server_t * HMS_fetch_servers (msg_server_t *list, int room, int id); +msg_server_t * HMS_fetch_servers (msg_server_t *list, int id); int HMS_compare_mod_version (char *buffer, size_t size_of_buffer); #endif diff --git a/src/p_maputl.c b/src/p_maputl.c index 0ae0b0dbc..de6feb143 100644 --- a/src/p_maputl.c +++ b/src/p_maputl.c @@ -432,8 +432,17 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj) return; } - front = linedef->frontsector; - back = linedef->backsector; + // Treat polyobjects kind of like 3D Floors + if (linedef->polyobj && (linedef->polyobj->flags & POF_TESTHEIGHT)) + { + front = linedef->frontsector; + back = linedef->frontsector; + } + else + { + front = linedef->frontsector; + back = linedef->backsector; + } I_Assert(front != NULL); I_Assert(back != NULL); diff --git a/src/p_mobj.c b/src/p_mobj.c index 15acc1cb7..8d119d251 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -10464,6 +10464,7 @@ static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing) return true; } else if (mthing->type == 750 // Slope vertex point (formerly chaos spawn) + || (mthing->type == 777 || mthing->type == 778) // Slope anchors || (mthing->type >= 600 && mthing->type <= 609) // Special placement patterns || mthing->type == 1705 || mthing->type == 1713) // Hoops { diff --git a/src/p_saveg.c b/src/p_saveg.c index 7379a02c8..e2988e4d3 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2123,7 +2123,7 @@ static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type WRITEFIXED(save_p, ht->extent); WRITEMEM(save_p, ht->tags, sizeof(ht->tags)); - WRITEMEM(save_p, ht->vex, sizeof(ht->vex)); + WRITEMEM(save_p, ht->vex, sizeof(ht->vex)); } static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type) diff --git a/src/p_setup.c b/src/p_setup.c index b1e53c361..9b8697b50 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3980,8 +3980,16 @@ boolean P_LoadLevel(boolean fromnetsave) // init anything that P_SpawnSlopes/P_LoadThings needs to know P_InitSpecials(); + // set up world state + // jart: needs to be done here so anchored slopes know the attached list + P_SpawnSpecials(fromnetsave); + P_SpawnSlopes(fromnetsave); + P_RaiseThings(); + + P_SpawnSpecialsThatRequireObjects(); + P_SpawnMapThings(!fromnetsave); skyboxmo[0] = skyboxviewpnts[0]; skyboxmo[1] = skyboxcenterpnts[0]; @@ -3990,8 +3998,6 @@ boolean P_LoadLevel(boolean fromnetsave) if (!playerstarts[numcoopstarts]) break; - // set up world state - P_SpawnSpecials(fromnetsave); K_AdjustWaypointsParameters(); if (!fromnetsave) // ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame) diff --git a/src/p_slopes.c b/src/p_slopes.c index a61612ee7..24333dbf8 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -27,6 +27,9 @@ pslope_t *slopelist = NULL; UINT16 slopecount = 0; +static void P_BuildSlopeAnchorList (void); +static void P_SetupAnchoredSlopes (void); + // Calculate line normal void P_CalculateSlopeNormal(pslope_t *slope) { slope->normal.z = FINECOSINE(slope->zangle>>ANGLETOFINESHIFT); @@ -72,7 +75,7 @@ static void P_CalculateLineSlopeHighLow(pslope_t *slope, line_t *line, boolean c } /// Setup slope via 3 vertexes. -static void ReconfigureViaVertexes (pslope_t *slope, const vector3_t v1, const vector3_t v2, const vector3_t v3) +void P_ReconfigureViaVertexes (pslope_t *slope, const vector3_t v1, const vector3_t v2, const vector3_t v3) { vector3_t vec1, vec2; @@ -185,7 +188,7 @@ void T_DynamicSlopeVert (dynplanethink_t* th) th->vex[i].z = 0; } - ReconfigureViaVertexes(slope, th->vex[0], th->vex[1], th->vex[2]); + P_ReconfigureViaVertexes(slope, th->vex[0], th->vex[1], th->vex[2]); } static inline void P_AddDynSlopeThinker (pslope_t* slope, dynplanetype_t type, line_t* sourceline, fixed_t extent, const INT16 tags[3], const vector3_t vx[3]) @@ -492,7 +495,7 @@ static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flag vx[i].z += R_PointInSubsector(vx[i].x, vx[i].y)->sector->floorheight; } - ReconfigureViaVertexes(ret, vx[0], vx[1], vx[2]); + P_ReconfigureViaVertexes(ret, vx[0], vx[1], vx[2]); if (spawnthinker && (flags & SL_DYNAMIC)) P_AddDynSlopeThinker(ret, DP_VERTEX, NULL, 0, tags, vx); @@ -574,7 +577,7 @@ static void SpawnVertexSlopes(void) pslope_t *slop = Slope_Add(0); sc->f_slope = slop; sc->hasslope = true; - ReconfigureViaVertexes(slop, vtx[0], vtx[1], vtx[2]); + P_ReconfigureViaVertexes(slop, vtx[0], vtx[1], vtx[2]); } if (v1->ceilingzset || v2->ceilingzset || v3->ceilingzset) @@ -586,7 +589,7 @@ static void SpawnVertexSlopes(void) pslope_t *slop = Slope_Add(0); sc->c_slope = slop; sc->hasslope = true; - ReconfigureViaVertexes(slop, vtx[0], vtx[1], vtx[2]); + P_ReconfigureViaVertexes(slop, vtx[0], vtx[1], vtx[2]); } } } @@ -709,6 +712,14 @@ void P_SpawnSlopes(const boolean fromsave) { } } + // jart + + /// Build list of slope anchors--faster searching. + P_BuildSlopeAnchorList(); + + /// Setup anchor based slopes. + P_SetupAnchoredSlopes(); + /// Copies slopes from tagged sectors via line specials. /// \note Doesn't actually copy, but instead they share the same pointers. for (i = 0; i < numlines; i++) @@ -966,3 +977,6 @@ void P_ButteredSlope(mobj_t *mo) P_Thrust(mo, mo->standingslope->xydirection, thrust); } + +// jart +#include "slope_anchors.c" diff --git a/src/p_slopes.h b/src/p_slopes.h index 46e8dc1e7..ef8cb5886 100644 --- a/src/p_slopes.h +++ b/src/p_slopes.h @@ -50,6 +50,7 @@ typedef enum void P_LinkSlopeThinkers (void); void P_CalculateSlopeNormal(pslope_t *slope); +void P_ReconfigureViaVertexes(pslope_t *slope, const vector3_t v1, const vector3_t v2, const vector3_t v3); void P_SpawnSlopes(const boolean fromsave); // diff --git a/src/p_spec.c b/src/p_spec.c index 214b7bcd7..3a73aac2e 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1081,7 +1081,6 @@ INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start) } } - // Parses arguments for parameterized polyobject door types static boolean PolyDoor(line_t *line) { @@ -1355,7 +1354,6 @@ static boolean PolyDisplace(line_t *line) return EV_DoPolyObjDisplace(&pdd); } - // Parses arguments for parameterized polyobject rotate-by-sector-heights specials static boolean PolyRotDisplace(line_t *line) { @@ -5547,6 +5545,26 @@ P_RaiseTaggedThingsToFakeFloor ( } } +void +P_RaiseThings (void) +{ + size_t i; + + for (i = 0; i < numlines; ++i) + { + switch (lines[i].special) + { + case 80: // Raise tagged things by type to this FOF + P_RaiseTaggedThingsToFakeFloor( + ( sides[lines[i].sidenum[0]].textureoffset >> FRACBITS ), + lines[i].tag, + lines[i].frontsector + ); + break; + } + } +} + // // SPECIAL SPAWNING // @@ -6931,28 +6949,19 @@ void P_SpawnSpecials(boolean fromnetsave) } } - /* some things have to be done after FOF spawn */ - - for (i = 0; i < numlines; ++i) - { - switch (lines[i].special) - { - case 80: // Raise tagged things by type to this FOF - P_RaiseTaggedThingsToFakeFloor( - ( sides[lines[i].sidenum[0]].textureoffset >> FRACBITS ), - lines[i].tag, - lines[i].frontsector - ); - break; - } - } - // Allocate each list for (i = 0; i < numsectors; i++) if(secthinkers[i].thinkers) Z_Free(secthinkers[i].thinkers); Z_Free(secthinkers); +} + +/** Fuck polyobjects + */ +void P_SpawnSpecialsThatRequireObjects(void) +{ + size_t i; // haleyjd 02/20/06: spawn polyobjects Polyobj_InitLevel(); diff --git a/src/p_spec.h b/src/p_spec.h index 9dccc0d8a..10cc302f7 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -43,6 +43,8 @@ void P_SetupLevelFlatAnims(void); // at map load void P_InitSpecials(void); void P_SpawnSpecials(boolean fromnetsave); +void P_RaiseThings(void); +void P_SpawnSpecialsThatRequireObjects(void); // every tic void P_UpdateSpecials(void); @@ -648,4 +650,12 @@ void P_CalcHeight(player_t *player); sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo); +/* line specials */ +enum +{ + LT_SLOPE_ANCHORS_FLOOR = 777, + LT_SLOPE_ANCHORS_CEILING = 778, + LT_SLOPE_ANCHORS = 779, +}; + #endif diff --git a/src/s_sound.c b/src/s_sound.c index 6ef49a1a3..048baf878 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -536,6 +536,7 @@ void S_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan) void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) { + const INT32 initial_volume = volume; INT32 sep, pitch, priority, cnum; const sfxenum_t actual_id = sfx_id; sfxinfo_t *sfx; @@ -900,6 +901,7 @@ dontplay4: // Assigns the handle to one of the channels in the // mix/output buffer. + channels[cnum].volume = initial_volume; channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority, cnum); } @@ -1110,7 +1112,7 @@ void S_UpdateSounds(void) if (I_SoundIsPlaying(c->handle)) { // initialize parameters - volume = 255; // 8 bits internal volume precision + volume = c->volume; // 8 bits internal volume precision pitch = NORM_PITCH; sep = NORM_SEP; @@ -1444,15 +1446,12 @@ INT32 S_AdjustSoundParams(const mobj_t *listener, const mobj_t *source, INT32 *v } // volume calculation - if (approx_dist < S_CLOSE_DIST) - { - // SfxVolume is now hardware volume - *vol = 255; // not snd_SfxVolume - } - else + /* not sure if it should be > (no =), but this matches the old behavior */ + if (approx_dist >= S_CLOSE_DIST) { // distance effect - *vol = (15 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS)) / S_ATTENUATOR; + INT32 n = (15 * ((S_CLIPPING_DIST - approx_dist)>>FRACBITS)); + *vol = FixedMul(*vol * FRACUNIT / 255, n) / S_ATTENUATOR; } if (r_splitscreen) diff --git a/src/s_sound.h b/src/s_sound.h index c8b793596..bbf50210d 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -88,6 +88,9 @@ typedef struct // origin of sound const void *origin; + // initial volume of sound, which is applied after distance and direction + INT32 volume; + // handle of the sound being played INT32 handle; diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index 72f78188f..02dd74a4d 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -75,6 +75,8 @@ if(${SDL2_FOUND}) set(SRB2_SDL2_TOTAL_SOURCES ${SRB2_CORE_SOURCES} ${SRB2_CORE_HEADERS} + ${SRB2_DISCORDRPC_SOURCES} + ${SRB2_DISCORDRPC_HEADERS} ${SRB2_PNG_SOURCES} ${SRB2_PNG_HEADERS} ${SRB2_CORE_RENDER_SOURCES} @@ -91,9 +93,11 @@ if(${SDL2_FOUND}) ${SRB2_PNG_SOURCES} ${SRB2_PNG_HEADERS}) source_group("Renderer" FILES ${SRB2_CORE_RENDER_SOURCES}) source_group("Game" FILES ${SRB2_CORE_GAME_SOURCES}) + source_group("Discord Rich Presence" FILES ${SRB2_DISCORDRPC_SOURCES} ${SRB2_DISCORDRPC_HEADERS}) source_group("Assembly" FILES ${SRB2_ASM_SOURCES} ${SRB2_NASM_SOURCES}) source_group("LUA" FILES ${SRB2_LUA_SOURCES} ${SRB2_LUA_HEADERS}) source_group("LUA\\Interpreter" FILES ${SRB2_BLUA_SOURCES} ${SRB2_BLUA_HEADERS}) + if(${SRB2_CONFIG_HWRENDER}) set(SRB2_SDL2_TOTAL_SOURCES ${SRB2_SDL2_TOTAL_SOURCES} @@ -166,6 +170,7 @@ if(${SDL2_FOUND}) ${ZLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${CURL_LIBRARIES} + ${DISCORDRPC_LIBRARIES} ) set_target_properties(SRB2SDL2 PROPERTIES OUTPUT_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") else() @@ -179,6 +184,7 @@ if(${SDL2_FOUND}) ${ZLIB_LIBRARIES} ${OPENGL_LIBRARIES} ${CURL_LIBRARIES} + ${DISCORDRPC_LIBRARIES} ) if(${CMAKE_SYSTEM} MATCHES Linux) @@ -261,6 +267,7 @@ if(${SDL2_FOUND}) ${ZLIB_INCLUDE_DIRS} ${OPENGL_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} + ${DISCORDRPC_INCLUDE_DIRS} ) if((${SRB2_HAVE_MIXER}) OR (${SRB2_HAVE_MIXERX})) @@ -375,6 +382,10 @@ 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/Makefile.cfg b/src/sdl/Makefile.cfg index 68e616f01..45d0d6ba7 100644 --- a/src/sdl/Makefile.cfg +++ b/src/sdl/Makefile.cfg @@ -53,28 +53,6 @@ ifndef NOHW OBJS+=$(OBJDIR)/r_opengl.o $(OBJDIR)/ogl_sdl.o endif -ifndef NOHS -ifdef OPENAL - OBJS+=$(OBJDIR)/s_openal.o - OPTS+=-DSTATIC3DS - STATICHS=1 -else -ifdef FMOD - OBJS+=$(OBJDIR)/s_fmod.o - OPTS+=-DSTATIC3DS - STATICHS=1 -else -ifdef MINGW -ifdef DS3D - OBJS+=$(OBJDIR)/s_ds3d.o - OPTS+=-DSTATIC3DS - STATICHS=1 -endif -endif -endif -endif -endif - ifdef NOMIXER i_sound_o=$(OBJDIR)/sdl_sound.o else diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c index 5502e083c..5b205503e 100644 --- a/src/sdl/i_main.c +++ b/src/sdl/i_main.c @@ -65,6 +65,10 @@ char logfilename[1024]; #endif #endif +#if defined (_WIN32) +#include "exchndl.h" +#endif + #if defined (_WIN32) #include "../win32/win_dbg.h" typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID); @@ -191,6 +195,20 @@ static void InitLogging(void) #endif +#ifdef _WIN32 +static void +ChDirToExe (void) +{ + CHAR path[MAX_PATH]; + if (GetModuleFileNameA(NULL, path, MAX_PATH) > 0) + { + strrchr(path, '\\')[0] = '\0'; + SetCurrentDirectoryA(path); + } +} +#endif + + /** \brief The main function \param argc number of arg @@ -219,6 +237,10 @@ int main(int argc, char **argv) #endif #endif +#ifdef _WIN32 + ChDirToExe(); +#endif + #ifdef LOGMESSAGES if (!M_CheckParm("-nolog")) InitLogging(); @@ -237,7 +259,7 @@ int main(int argc, char **argv) ) #endif { - LoadLibraryA("exchndl.dll"); + ExcHndlInit(); } } #ifndef __MINGW32__ diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 47c7c5e20..6f73ec364 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -152,11 +152,6 @@ int TimeFunction(int requested_frequency); #define DEFAULTSEARCHPATH1 "/usr/local/games" #define DEFAULTSEARCHPATH2 "/usr/games" #define DEFAULTSEARCHPATH3 "/usr/local" -#elif defined (_WIN32) -#define DEFAULTWADLOCATION1 "c:\\games\\srb2kart" -#define DEFAULTWADLOCATION2 "\\games\\srb2kart" -#define DEFAULTSEARCHPATH1 "c:\\games" -#define DEFAULTSEARCHPATH2 "\\games" #endif /** \brief WAD file to look for @@ -2341,6 +2336,18 @@ static const char *locateWad(void) #endif +#ifdef DEFAULTDIR + I_OutputMsg(",HOME/" DEFAULTDIR); + // examine user jart directory + if ((envstr = I_GetEnv("HOME")) != NULL) + { + sprintf(returnWadPath, "%s" PATHSEP DEFAULTDIR, envstr); + if (isWadPathOk(returnWadPath)) + return returnWadPath; + } +#endif + + #ifdef CMAKECONFIG #ifndef NDEBUG I_OutputMsg(","CMAKE_ASSETS_DIR); diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index bf61797f3..b1713f5f1 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -20,6 +20,7 @@ /// \brief SRB2 graphics stuff for SDL #include +#include #include @@ -85,6 +86,10 @@ #include "ogl_sdl.h" #endif +#ifdef HAVE_DISCORDRPC +#include "../discord.h" +#endif + // maximum number of windowed modes (see windowedModes[][]) #define MAXWINMODES (18) @@ -1324,6 +1329,11 @@ void I_FinishUpdate(void) if (cv_closedcaptioning.value) SCR_ClosedCaptions(); +#ifdef HAVE_DISCORDRPC + if (discordRequestList != NULL) + ST_AskToJoinEnvelope(); +#endif + if (rendermode == render_soft && screens[0]) { SDL_Rect rect; diff --git a/src/slope_anchors.c b/src/slope_anchors.c new file mode 100644 index 000000000..fef3b6e2d --- /dev/null +++ b/src/slope_anchors.c @@ -0,0 +1,492 @@ +// 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. +//----------------------------------------------------------------------------- +/// \brief Charyb's vertex slope anchors. +/// This file is self contained to avoid a Big Large merge conflict. + +/* +FIXME +FIXME +FIXME +FIXME WHEN 2.2 MERGE IS OVER, REFACTOR A LOT OF THE CODE IN P_SLOPES.C AND +FIXME MAKE THIS NOT REDUNDANT. +FIXME +FIXME +FIXME +*/ + +struct anchor_list +{ + mapthing_t ** anchors; + const vertex_t ** points; + fixed_t * closeness; + size_t count; +}; + +struct anchor_list floor_anchors; +struct anchor_list ceiling_anchors; + +static void * new_list (size_t n) { + return Z_Malloc(n, PU_LEVEL, NULL); +} + +static void make_new_anchor_list (struct anchor_list * list) { + list->anchors = new_list(list->count * sizeof *list->anchors); + list->points = new_list(list->count * sizeof *list->points); + list->closeness = new_list(list->count * sizeof *list->closeness); +} + +static void allocate_anchors (void) { + size_t i; + + floor_anchors.count = 0; + ceiling_anchors.count = 0; + + for (i = 0; i < nummapthings; ++i) + { + switch (mapthings[i].type) + { + case FLOOR_SLOPE_THING: + floor_anchors.count++; + break; + + case CEILING_SLOPE_THING: + ceiling_anchors.count++; + break; + } + } + + make_new_anchor_list(&floor_anchors); + make_new_anchor_list(&ceiling_anchors); +} + +static void +compare_vertex_distance +( + const vertex_t ** nearest, + fixed_t * nearest_distance, + const fixed_t origin_x, + const fixed_t origin_y, + const vertex_t * v +){ + const fixed_t distance = abs(P_AproxDistance + ( + origin_x - v->x, + origin_y - v->y + )); + + if (distance < (*nearest_distance)) + { + (*nearest) = v; + (*nearest_distance) = distance; + } +} + +static const vertex_t * +nearest_point +( + fixed_t * closeness, + mapthing_t * a, + const sector_t * sector +){ + const fixed_t x = a->x << FRACBITS; + const fixed_t y = a->y << FRACBITS; + + const vertex_t * v = NULL;/* shut compiler up, should never be NULL */ + + size_t i; + + (*closeness) = INT32_MAX; + + for (i = 0; i < sector->linecount; ++i) + { + compare_vertex_distance(&v, closeness, x, y, sector->lines[i]->v1); + compare_vertex_distance(&v, closeness, x, y, sector->lines[i]->v2); + } + + return v; +} + +static INT16 +anchor_height +( + const mapthing_t * a, + const sector_t * s +){ + { + INT16 z = ( a->options >> ZSHIFT ); + + if (a->options & MTF_OBJECTFLIP) + { + return ( s->ceilingheight >> FRACBITS ) - z; + } + else + { + return ( s->floorheight >> FRACBITS ) + z; + } + } +} + +static void +set_anchor +( + struct anchor_list * list, + mapthing_t * a +){ + const subsector_t * sub = R_PointInSubsector + ( + a->x << FRACBITS, + a->y << FRACBITS + ); + + const vertex_t * v; + + fixed_t closeness; + + a->z = anchor_height(a, sub->sector); + + v = nearest_point(&closeness, a, sub->sector); + + a->x = ( v->x >> FRACBITS ); + a->y = ( v->y >> FRACBITS ); + + list->anchors [list->count] = a; + list->points [list->count] = v; + list->closeness[list->count] = closeness; + + list->count++; +} + +static void build_anchors (void) { + size_t i; + + floor_anchors.count = 0; + ceiling_anchors.count = 0; + + for (i = 0; i < nummapthings; ++i) + { + switch (mapthings[i].type) + { + case FLOOR_SLOPE_THING: + set_anchor(&floor_anchors, &mapthings[i]); + break; + + case CEILING_SLOPE_THING: + set_anchor(&ceiling_anchors, &mapthings[i]); + break; + } + } +} + +static void +get_anchor +( + mapthing_t ** anchors, + fixed_t distances[3], + const struct anchor_list * list, + const INT16 tag, + const vertex_t * v +){ + size_t i; + + int k; + + for (i = 0; i < list->count; ++i) + { + if (list->points[i] == v && list->anchors[i]->extrainfo == tag) + { + for (k = 0; k < 3; ++k) + { + if (list->closeness[i] < distances[k]) + { + if (k == 0) + { + distances[2] = distances[1]; + distances[1] = distances[0]; + + anchors [2] = anchors [1]; + anchors [1] = anchors [0]; + } + else if (k == 1) + { + distances[2] = distances[1]; + anchors [2] = anchors [1]; + } + + distances[k] = list->closeness[i]; + + anchors[k] = list->anchors[i]; + + break; + } + else if (list->anchors[i] == anchors[k]) + { + break; + } + } + } + } +} + +static void +get_sector_anchors +( + mapthing_t ** anchors, + fixed_t distances[3], + const struct anchor_list * list, + const INT16 tag, + const sector_t * sector +){ + size_t i; + + for (i = 0; i < sector->linecount; ++i) + { + get_anchor(anchors, distances, list, tag, sector->lines[i]->v1); + get_anchor(anchors, distances, list, tag, sector->lines[i]->v2); + } +} + +static mapthing_t ** +find_closest_anchors +( + const sector_t * sector, + const struct anchor_list * list, + const INT16 tag +){ + fixed_t distances[3] = { INT32_MAX, INT32_MAX, INT32_MAX }; + + mapthing_t ** anchors; + + int last = 0; + + size_t i; + + if (list->count < 3) + { + I_Error("At least three slope anchors are required to make a slope."); + } + + anchors = Z_Malloc(3 * sizeof *anchors, PU_LEVEL, NULL); + + if (sector->numattached > 0) + { + for (i = 0; i < sector->numattached; ++i) + { + get_sector_anchors + (anchors, distances, list, tag, §ors[sector->attached[i]]); + } + } + else + { + get_sector_anchors(anchors, distances, list, tag, sector); + } + + if (distances[2] < INT32_MAX) + { + return anchors; + } + + if (distances[1] < INT32_MAX) + last = 2; + else if (distances[0] < INT32_MAX) + last = 1; + else + last = 0; + + if (sector->numattached > 0) + { + CONS_Printf("\nSearched for anchors in sectors...\n\n"); + + for (i = 0; i < sector->numattached; ++i) + { + CONS_Printf("#%s\n", sizeu1 (sector->attached[i])); + } + + I_Error( + "(Control Sector #%s)" + " Slope requires anchors (with Parameter %d)" + " near 3 of its target sectors' vertices (%d found)" + + "\n\nCheck the log to see which sectors were searched.", + + sizeu1 (sector - sectors), + tag, + last + ); + } + else + { + I_Error( + "(Sector #%s)" + " Slope requires anchors (with Parameter %d)" + " near 3 of its vertices (%d found)", + + sizeu1 (sector - sectors), + tag, + last + ); + } +} + +static pslope_t * +new_vertex_slope +( + mapthing_t ** anchors, + const INT16 flags +){ + pslope_t * slope = Z_Calloc(sizeof (pslope_t), PU_LEVEL, NULL); + const vector3_t anchorVertices[3] = { + {anchors[0]->x << FRACBITS, anchors[0]->y << FRACBITS, anchors[0]->z << FRACBITS}, + {anchors[1]->x << FRACBITS, anchors[1]->y << FRACBITS, anchors[1]->z << FRACBITS}, + {anchors[2]->x << FRACBITS, anchors[2]->y << FRACBITS, anchors[2]->z << FRACBITS} + }; + + if (flags & ML_NETONLY) + { + slope->flags |= SL_NOPHYSICS; + } + + if (flags & ML_NONET) + { + slope->flags |= SL_DYNAMIC; + } + + P_ReconfigureViaVertexes(slope, anchorVertices[0], anchorVertices[1], anchorVertices[2]); + //slope->refpos = 5; + + // Add to the slope list + slope->next = slopelist; + slopelist = slope; + + slopecount++; + slope->id = slopecount; + + return slope; +} + +static mapthing_t ** +flip_slope +( + mapthing_t ** origin, + const sector_t * sector +){ + mapthing_t * copy = Z_Malloc(3 * sizeof (mapthing_t), PU_LEVEL, NULL); + mapthing_t ** anchors = Z_Malloc(3 * sizeof (mapthing_t *), PU_LEVEL, NULL); + + size_t i; + + for (i = 0; i < 3; ++i) + { + memcpy(©[i], origin[i], sizeof copy[i]); + + copy[i].options ^= MTF_OBJECTFLIP; + copy[i].z = anchor_height(©[i], sector); + + anchors[i] = ©[i]; + } + + return anchors; +} + +static void +slope_sector +( + pslope_t ** slope, + pslope_t ** alt, + sector_t * sector, + const INT16 flags, + const struct anchor_list * list, + const INT16 tag +){ + mapthing_t ** anchors = find_closest_anchors(sector, list, tag); + + if (anchors != NULL) + { + (*slope) = new_vertex_slope(anchors, flags); + + /* Effect 6 - invert slope to opposite side */ + if (flags & ML_EFFECT6) + { + (*alt) = new_vertex_slope(flip_slope(anchors, sector), flags); + } + + sector->hasslope = true; + } +} + +static void +make_anchored_slope +( + const line_t * line, + const int plane +){ + enum + { + FLOOR = 0x1, + CEILING = 0x2, + }; + + INT16 flags = line->flags; + + const int side = ( flags & ML_NOCLIMB ) != 0; + + sector_t * s; + + if (side == 0 || flags & ML_TWOSIDED) + { + s = sides[line->sidenum[side]].sector; + + if (plane == (FLOOR|CEILING)) + { + flags &= ~ML_EFFECT6; + } + + if (plane & FLOOR) + { + slope_sector + (&s->f_slope, &s->c_slope, s, flags, &floor_anchors, line->tag); + } + + if (plane & CEILING) + { + slope_sector + (&s->c_slope, &s->f_slope, s, flags, &ceiling_anchors, line->tag); + } + } +} + +static void P_BuildSlopeAnchorList (void) { + allocate_anchors(); + build_anchors(); +} + +static void P_SetupAnchoredSlopes (void) { + enum + { + FLOOR = 0x1, + CEILING = 0x2, + }; + + size_t i; + + for (i = 0; i < numlines; ++i) + { + if (lines[i].special == LT_SLOPE_ANCHORS_FLOOR) + { + make_anchored_slope(&lines[i], FLOOR); + } + else if (lines[i].special == LT_SLOPE_ANCHORS_CEILING) + { + make_anchored_slope(&lines[i], CEILING); + } + else if (lines[i].special == LT_SLOPE_ANCHORS) + { + make_anchored_slope(&lines[i], FLOOR|CEILING); + } + } +} diff --git a/src/sounds.c b/src/sounds.c index a6a788ffc..d06e521d0 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -888,6 +888,10 @@ sfxinfo_t S_sfx[NUMSFX] = {"bhurry", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // v1.0.2 Battle overtime {"bsnipe", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Banana sniping {"sploss", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Down to yellow sparks + {"join", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Player joined server + {"leave", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Player left server + {"requst", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Got a Discord join request + {"syfail", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Funny sync failure {"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // :shitsfree: {"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Debug notification {"cock", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Hammer cocks, bang bang diff --git a/src/sounds.h b/src/sounds.h index a9ced4874..51155c7c3 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -952,6 +952,10 @@ typedef enum sfx_bhurry, sfx_bsnipe, sfx_sploss, + sfx_join, + sfx_leave, + sfx_requst, + sfx_syfail, sfx_itfree, sfx_dbgsal, sfx_cock, diff --git a/src/st_stuff.c b/src/st_stuff.c index d7bcb0744..a8e7d2434 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -136,6 +136,11 @@ static patch_t *fnshico; static patch_t *hud_tv1; static patch_t *hud_tv2; +#ifdef HAVE_DISCORDRPC +// Discord Rich Presence +static patch_t *envelope; +#endif + // SRB2kart hudinfo_t hudinfo[NUMHUDITEMS] = @@ -363,6 +368,11 @@ void ST_LoadGraphics(void) // Midnight Channel: hud_tv1 = W_CachePatchName("HUD_TV1", PU_HUDGFX); hud_tv2 = W_CachePatchName("HUD_TV2", PU_HUDGFX); + +#ifdef HAVE_DISCORDRPC + // Discord Rich Presence + envelope = W_CachePatchName("K_REQUES", PU_HUDGFX); +#endif } // made separate so that skins code can reload custom face graphics @@ -1009,6 +1019,22 @@ static void ST_MayonakaStatic(void) V_DrawFixedPatch(320<