diff --git a/CMakeLists.txt b/CMakeLists.txt
index 51f98707d..1e80b549e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
# DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string.
# Version change is fine.
project(SRB2
- VERSION 1.2
+ VERSION 1.3
LANGUAGES C)
if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/appveyor.yml b/appveyor.yml
index 4ad24ee05..7e87c271c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 1.2.{branch}-{build}
+version: 1.3.{branch}-{build}
os: MinGW
environment:
@@ -29,7 +29,7 @@ environment:
##############################
DPL_ENABLED: 0
DPL_TAG_ENABLED: 0
- DPL_INSTALLER_NAME: srb2kart-v12
+ DPL_INSTALLER_NAME: srb2kart-v13
# Asset handling is barebones vs. Travis Deployer. We operate on 7z only.
# Include the README files and the OpenGL batch in the main and patch archives.
# The x86/x64 archives contain the DLL binaries.
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/debian-template/source/options b/debian-template/source/options
index 7829e2976..9532ff202 100644
--- a/debian-template/source/options
+++ b/debian-template/source/options
@@ -2,10 +2,7 @@ tar-ignore = "assets/*.srb"
tar-ignore = "assets/*.pk3"
tar-ignore = "assets/*.dta"
tar-ignore = "assets/*.wad"
-<<<<<<< HEAD:debian-template/source/options
tar-ignore = "assets/*.kart"
-=======
->>>>>>> e251f9c230beda984cdcdea7e903d765f1c68f6f:debian-template/source/options
tar-ignore = "assets/debian/${PACKAGE_NAME}-data/*"
tar-ignore = "assets/debian/tmp/*"
tar-ignore = "*.obj"
diff --git a/libs/DLL-README.txt b/libs/DLL-README.txt
index 06fae1278..bbb6d3cd4 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/4/2018 (v2.1.21)
+Updated 8/23/2020 (v1.3)
Here are the required DLLs, per build. For each architecture, copy all the binaries from these folders:
@@ -14,6 +14,7 @@ and don't forget to build r_opengl.dll for srb2dd.
* libs\dll-binaries\i686\exchndl.dll
* libs\dll-binaries\i686\libgme.dll
+* libs\dll-binaries\i686\discord-rpc.dll
* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll)
* libs\SDL2\i686-w64-mingw32\bin\SDL2.dll
* libs\SDL2_mixer\i686-w64-mingw32\bin\*.dll (get everything)
@@ -22,22 +23,7 @@ and don't forget to build r_opengl.dll for srb2dd.
* libs\dll-binaries\x86_64\exchndl.dll
* libs\dll-binaries\x86_64\libgme.dll
+* libs\dll-binaries\x86_64\discord-rpc.dll
* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll)
* libs\SDL2\x86_64-w64-mingw32\bin\SDL2.dll
* libs\SDL2_mixer\x86_64-w64-mingw32\bin\*.dll (get everything)
-
-## srb2kartdd, 32-bit
-
-* libs\dll-binaries\i686\exchndl.dll
-* libs\dll-binaries\i686\fmodex.dll
-* libs\dll-binaries\i686\libgme.dll
-* libs\dll-binaries\i686\mgwhelp.dll (depend for exchndl.dll)
-* r_opengl.dll (build this from make)
-
-## srb2kartdd, 64-bit
-
-* libs\dll-binaries\x86_64\exchndl.dll
-* libs\dll-binaries\x86_64\fmodex.dll
-* libs\dll-binaries\x86_64\libgme.dll
-* libs\dll-binaries\x86_64\mgwhelp.dll (depend for exchndl.dll)
-* r_opengl.dll (build this from make)
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/src/CMakeLists.txt b/src/CMakeLists.txt
index c0d673a6f..c43464b78 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -35,6 +35,7 @@ set(SRB2_CORE_SOURCES
m_random.c
md5.c
mserv.c
+ http-mserv.c
s_sound.c
screen.c
sounds.c
@@ -220,6 +221,8 @@ set(SRB2_CONFIG_HAVE_ZLIB ON CACHE BOOL
"Enable zlib support.")
set(SRB2_CONFIG_HAVE_GME ON CACHE BOOL
"Enable GME support.")
+set(SRB2_CONFIG_HAVE_DISCORDRPC OFF CACHE BOOL
+ "Enable Discord rich presence support.")
set(SRB2_CONFIG_HAVE_CURL ON CACHE BOOL
"Enable curl support, used for downloading files via HTTP.")
set(SRB2_CONFIG_HWRENDER ON CACHE BOOL
@@ -234,7 +237,7 @@ set(SRB2_CONFIG_STATIC_OPENGL OFF CACHE BOOL
### use internal libraries?
if(${CMAKE_SYSTEM} MATCHES "Windows") ###set on Windows only
set(SRB2_CONFIG_USE_INTERNAL_LIBRARIES OFF CACHE BOOL
- "Use SRB2's internal copies of required dependencies (SDL2, PNG, zlib, GME).")
+ "Use SRB2Kart's internal copies of required dependencies (SDL2, PNG, zlib, GME).")
endif()
if(${SRB2_CONFIG_HAVE_BLUA})
@@ -277,6 +280,7 @@ if(${SRB2_CONFIG_HAVE_BLUA})
blua/lfunc.c
blua/lgc.c
blua/linit.c
+ blua/liolib.c
blua/llex.c
blua/lmem.c
blua/lobject.c
@@ -328,7 +332,7 @@ if(${SRB2_CONFIG_HAVE_GME})
if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
set(GME_FOUND ON)
set(GME_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/gme/include)
- if(${SRB2_SYSTEM_BITS} EQUAL 64)
+ if(${SRB2_SYSTEM_BITS} EQUAL 64)
set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win64 -lgme")
else() # 32-bit
set(GME_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/gme/win32 -lgme")
@@ -344,6 +348,32 @@ if(${SRB2_CONFIG_HAVE_GME})
endif()
endif()
+if(${SRB2_CONFIG_HAVE_DISCORDRPC})
+ if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
+ set(DISCORDRPC_FOUND ON)
+ if(${SRB2_SYSTEM_BITS} EQUAL 64)
+ set(DISCORDRPC_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/discord-rpc/win64-dynamic/include)
+ set(DISCORDRPC_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/discord-rpc/win64-dynamic/lib -ldiscord-rpc")
+ else() # 32-bit
+ set(DISCORDRPC_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/libs/discord-rpc/win32-dynamic/include)
+ set(DISCORDRPC_LIBRARIES "-L${CMAKE_SOURCE_DIR}/libs/discord-rpc/win32-dynamic/lib -ldiscord-rpc")
+ endif()
+ else()
+ find_package(DiscordRPC)
+ endif()
+ if(${DISCORDRPC_FOUND})
+ set(SRB2_HAVE_DISCORDRPC ON)
+ add_definitions(-DHAVE_DISCORDRPC)
+ set(SRB2_DISCORDRPC_SOURCES discord.c)
+ set(SRB2_DISCORDRPC_HEADERS discord.h)
+ prepend_sources(SRB2_DISCORDRPC_SOURCES)
+ prepend_sources(SRB2_DISCORDRPC_HEADERS)
+ source_group("Discord Rich Presence" FILES ${SRB2_DISCORDRPC_SOURCES} ${SRB2_DISCORDRPC_HEADERS})
+ else()
+ message(WARNING "You have specified that Discord Rich Presence is available but it was not found.")
+ endif()
+endif()
+
if(${SRB2_CONFIG_HAVE_ZLIB})
if(${SRB2_CONFIG_USE_INTERNAL_LIBRARIES})
set(ZLIB_FOUND ON)
@@ -396,7 +426,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 8eda8c400..fb859a339 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -60,10 +60,10 @@
# Compile with GCC 4.6x version, add 'GCC46=1'
# Compile a profile version, add 'PROFILEMODE=1'
# Compile a debug version, add 'DEBUGMODE=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)/srb2.srb \
@@ -136,7 +188,6 @@ NOPNG=1
NOZLIB=1
NONET=1
NOHW=1
-NOHS=1
NOASM=1
NOIPX=1
EXENAME?=srb2dummy
@@ -167,7 +218,6 @@ endif
ifdef PANDORA
NONX86=1
NOHW=1
-NOHS=1
endif
ifdef WII
@@ -217,7 +267,6 @@ NOPNG=1
NOZLIB=1
NONET=1
#NOHW=1
-NOHS=1
NOASM=1
NOIPX=1
NONX86=1
@@ -287,13 +336,6 @@ endif
$(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o
endif
-ifdef NOHS
- OPTS+=-DNOHS
-else
- OPTS+=-DHW3SOUND
- OBJS+=$(OBJDIR)/hw3sound.o
-endif
-
OPTS += -DCOMPVERSION
ifndef NONX86
@@ -396,6 +438,12 @@ CFLAGS+=-DHAVE_MINIUPNPC
endif
endif
+ifdef HAVE_DISCORDRPC
+LIBS+=-ldiscord-rpc
+CFLAGS+=-DHAVE_DISCORDRPC
+OBJS+=$(OBJDIR)/discord.o
+endif
+
ifndef NO_LUA
include blua/Makefile.cfg
endif
@@ -548,6 +596,7 @@ OBJS:=$(i_main_o) \
$(OBJDIR)/w_wad.o \
$(OBJDIR)/filesrch.o \
$(OBJDIR)/mserv.o \
+ $(OBJDIR)/http-mserv.o\
$(OBJDIR)/i_tcp.o \
$(OBJDIR)/lzf.o \
$(OBJDIR)/vid_copy.o \
@@ -941,19 +990,6 @@ $(OBJDIR)/r_minigl.o: hardware/r_minigl/r_minigl.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 a3baeedda..e45265cab 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -1,3 +1,4 @@
+# vim: ft=make
#
# Makefile.cfg for SRB2
#
@@ -7,7 +8,90 @@
# and other things
#
+# See the following variable don't start with 'GCC'. This is
+# to avoid a false positive with the version detection...
+
+SUPPORTED_GCC_VERSIONS:=\
+ 101 102\
+ 91 92 93\
+ 81 82 83 84\
+ 71 72 73 74 75\
+ 61 62 63 64\
+ 51 52 53 54 55\
+ 40 41 42 43 44 45 46 47 48 49
+
+LATEST_GCC_VERSION=10.2
+
+# gcc or g++
+ifdef PREFIX
+ CC=$(PREFIX)-gcc
+ CXX=$(PREFIX)-g++
+ OBJCOPY=$(PREFIX)-objcopy
+ OBJDUMP=$(PREFIX)-objdump
+ STRIP=$(PREFIX)-strip
+ WINDRES=$(PREFIX)-windres
+else
+ OBJCOPY=objcopy
+ OBJDUMP=objdump
+ STRIP=strip
+ WINDRES=windres
+endif
+
+# because Apple screws with us on this
+# need to get bintools from homebrew
+ifdef MACOSX
+ CC=clang
+ CXX=clang
+ OBJCOPY=gobjcopy
+ OBJDUMP=gobjdump
+endif
+
+# Automatically set version flag, but not if one was manually set
+ifeq (,$(filter GCC%,$(.VARIABLES)))
+ version:=$(shell $(CC) --version)
+ # check if this is in fact GCC
+ ifneq (,$(or $(findstring gcc,$(version)),$(findstring GCC,$(version))))
+ version:=$(shell $(CC) -dumpversion)
+
+ # Turn version into words of major, minor
+ v:=$(subst ., ,$(version))
+ # concat. major minor
+ v:=$(word 1,$(v))$(word 2,$(v))
+
+ # If this version is not in the list, default to the latest supported
+ ifeq (,$(filter $(v),$(SUPPORTED_GCC_VERSIONS)))
+ $(info\
+ Your compiler version, GCC $(version), is not supported by the Makefile.\
+ The Makefile will assume GCC $(LATEST_GCC_VERSION).)
+ GCC$(subst .,,$(LATEST_GCC_VERSION))=1
+ else
+ $(info Detected GCC $(version) (GCC$(v)))
+ GCC$(v)=1
+ endif
+ endif
+endif
+
+ifdef GCC102
+GCC101=1
+endif
+
+ifdef GCC101
+GCC93=1
+endif
+
+ifdef GCC93
+GCC92=1
+endif
+
+ifdef GCC92
+GCC91=1
+endif
+
ifdef GCC91
+GCC84=1
+endif
+
+ifdef GCC84
GCC83=1
endif
@@ -20,6 +104,18 @@ GCC81=1
endif
ifdef GCC81
+GCC75=1
+endif
+
+ifdef GCC75
+GCC74=1
+endif
+
+ifdef GCC74
+GCC73=1
+endif
+
+ifdef GCC73
GCC72=1
endif
@@ -44,6 +140,10 @@ GCC61=1
endif
ifdef GCC61
+GCC55=1
+endif
+
+ifdef GCC55
GCC54=1
endif
@@ -114,10 +214,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
@@ -463,30 +560,6 @@ ifdef ARCHNAME
BIN:=$(BIN)/$(ARCHNAME)
endif
-# gcc or g++
-ifdef PREFIX
- CC=$(PREFIX)-gcc
- CXX=$(PREFIX)-g++
- OBJCOPY=$(PREFIX)-objcopy
- OBJDUMP=$(PREFIX)-objdump
- STRIP=$(PREFIX)-strip
- WINDRES=$(PREFIX)-windres
-else
- OBJCOPY=objcopy
- OBJDUMP=objdump
- STRIP=strip
- WINDRES=windres
-endif
-
-# because Apple screws with us on this
-# need to get bintools from homebrew
-ifdef MACOSX
- CC=clang
- CXX=clang
- OBJCOPY=gobjcopy
- OBJDUMP=gobjdump
-endif
-
OBJDUMP_OPTS?=--wide --source --line-numbers
LD=$(CC)
diff --git a/src/am_map.c b/src/am_map.c
index 5e73d2ec4..91c68233c 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -862,10 +862,8 @@ static inline void AM_drawWalls(void)
{
size_t i;
static mline_t l;
-#ifdef ESLOPE
fixed_t frontf1,frontf2, frontc1, frontc2; // front floor/ceiling ends
fixed_t backf1 = 0, backf2 = 0, backc1 = 0, backc2 = 0; // back floor ceiling ends
-#endif
for (i = 0; i < numlines; i++)
{
@@ -873,7 +871,6 @@ static inline void AM_drawWalls(void)
l.a.y = lines[i].v1->y >> FRACTOMAPBITS;
l.b.x = lines[i].v2->x >> FRACTOMAPBITS;
l.b.y = lines[i].v2->y >> FRACTOMAPBITS;
-#ifdef ESLOPE
#define SLOPEPARAMS(slope, end1, end2, normalheight) \
if (slope) { \
end1 = P_GetZAt(slope, lines[i].v1->x, lines[i].v1->y); \
@@ -888,7 +885,6 @@ static inline void AM_drawWalls(void)
SLOPEPARAMS(lines[i].backsector->c_slope, backc1, backc2, lines[i].backsector->ceilingheight)
}
#undef SLOPEPARAMS
-#endif
if (!lines[i].backsector) // 1-sided
{
@@ -897,19 +893,11 @@ static inline void AM_drawWalls(void)
else
AM_drawMline(&l, WALLCOLORS);
}
-#ifdef ESLOPE
else if ((backf1 == backc1 && backf2 == backc2) // Back is thok barrier
|| (frontf1 == frontc1 && frontf2 == frontc2)) // Front is thok barrier
{
if (backf1 == backc1 && backf2 == backc2
&& frontf1 == frontc1 && frontf2 == frontc2) // BOTH are thok barriers
-#else
- else if (lines[i].backsector->floorheight == lines[i].backsector->ceilingheight // Back is thok barrier
- || lines[i].frontsector->floorheight == lines[i].frontsector->ceilingheight) // Front is thok barrier
- {
- if (lines[i].backsector->floorheight == lines[i].backsector->ceilingheight
- && lines[i].frontsector->floorheight == lines[i].frontsector->ceilingheight) // BOTH are thok barriers
-#endif
{
if (lines[i].flags & ML_NOCLIMB)
AM_drawMline(&l, NOCLIMBTSWALLCOLORS);
@@ -927,20 +915,10 @@ static inline void AM_drawWalls(void)
else
{
if (lines[i].flags & ML_NOCLIMB) {
-#ifdef ESLOPE
if (backf1 != frontf1 || backf2 != frontf2) {
-#else
- if (lines[i].backsector->floorheight
- != lines[i].frontsector->floorheight) {
-#endif
AM_drawMline(&l, NOCLIMBFDWALLCOLORS); // floor level change
}
-#ifdef ESLOPE
else if (backc1 != frontc1 || backc2 != frontc2) {
-#else
- else if (lines[i].backsector->ceilingheight
- != lines[i].frontsector->ceilingheight) {
-#endif
AM_drawMline(&l, NOCLIMBCDWALLCOLORS); // ceiling level change
}
else
@@ -948,20 +926,10 @@ static inline void AM_drawWalls(void)
}
else
{
-#ifdef ESLOPE
if (backf1 != frontf1 || backf2 != frontf2) {
-#else
- if (lines[i].backsector->floorheight
- != lines[i].frontsector->floorheight) {
-#endif
AM_drawMline(&l, FDWALLCOLORS); // floor level change
}
-#ifdef ESLOPE
else if (backc1 != frontc1 || backc2 != frontc2) {
-#else
- else if (lines[i].backsector->ceilingheight
- != lines[i].frontsector->ceilingheight) {
-#endif
AM_drawMline(&l, CDWALLCOLORS); // ceiling level change
}
else
diff --git a/src/blua/Makefile.cfg b/src/blua/Makefile.cfg
index b1131eaca..2ed5a5355 100644
--- a/src/blua/Makefile.cfg
+++ b/src/blua/Makefile.cfg
@@ -18,6 +18,7 @@ OBJS:=$(OBJS) \
$(OBJDIR)/ldo.o \
$(OBJDIR)/lfunc.o \
$(OBJDIR)/linit.o \
+ $(OBJDIR)/liolib.o \
$(OBJDIR)/llex.o \
$(OBJDIR)/lmem.o \
$(OBJDIR)/lobject.o \
diff --git a/src/blua/linit.c b/src/blua/linit.c
index 52b02dbe7..d17390b20 100644
--- a/src/blua/linit.c
+++ b/src/blua/linit.c
@@ -17,6 +17,7 @@
static const luaL_Reg lualibs[] = {
{"", luaopen_base},
{LUA_TABLIBNAME, luaopen_table},
+ {LUA_IOLIBNAME, luaopen_io},
{LUA_STRLIBNAME, luaopen_string},
{NULL, NULL}
};
diff --git a/src/blua/liolib.c b/src/blua/liolib.c
new file mode 100644
index 000000000..a9e71f74a
--- /dev/null
+++ b/src/blua/liolib.c
@@ -0,0 +1,589 @@
+/*
+** $Id: liolib.c,v 2.73.1.3 2008/01/18 17:47:43 roberto Exp $
+** Standard I/O (and system) library
+** See Copyright Notice in lua.h
+*/
+
+
+#include
+#include
+#include
+#include
+
+#define liolib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+#include "../i_system.h"
+#include "../doomdef.h"
+#include "../m_misc.h"
+
+
+
+#define IO_INPUT 1
+#define IO_OUTPUT 2
+
+#define FILELIMIT 1024*1024 // Size limit for reading/writing files
+
+
+static const char *const fnames[] = {"input", "output"};
+static const char *whitelist[] = { // Allow scripters to write files of these types to SRB2's folder
+ ".txt",
+ ".sav2",
+ ".cfg",
+ ".png",
+ ".bmp"
+};
+
+
+static int pushresult (lua_State *L, int i, const char *filename) {
+ int en = errno; /* calls to Lua API may change this value */
+ if (i) {
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+ else {
+ lua_pushnil(L);
+ if (filename)
+ lua_pushfstring(L, "%s: %s", filename, strerror(en));
+ else
+ lua_pushfstring(L, "%s", strerror(en));
+ lua_pushinteger(L, en);
+ return 3;
+ }
+}
+
+
+static void fileerror (lua_State *L, int arg, const char *filename) {
+ lua_pushfstring(L, "%s: %s", filename, strerror(errno));
+ luaL_argerror(L, arg, lua_tostring(L, -1));
+}
+
+
+#define tofilep(L) ((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE))
+
+
+static int io_type (lua_State *L) {
+ void *ud;
+ luaL_checkany(L, 1);
+ ud = lua_touserdata(L, 1);
+ lua_getfield(L, LUA_REGISTRYINDEX, LUA_FILEHANDLE);
+ if (ud == NULL || !lua_getmetatable(L, 1) || !lua_rawequal(L, -2, -1))
+ lua_pushnil(L); /* not a file */
+ else if (*((FILE **)ud) == NULL)
+ lua_pushliteral(L, "closed file");
+ else
+ lua_pushliteral(L, "file");
+ return 1;
+}
+
+
+static FILE *tofile (lua_State *L) {
+ FILE **f = tofilep(L);
+ if (*f == NULL)
+ luaL_error(L, "attempt to use a closed file");
+ return *f;
+}
+
+
+
+/*
+** When creating file handles, always creates a `closed' file handle
+** before opening the actual file; so, if there is a memory error, the
+** file is not left opened.
+*/
+static FILE **newfile (lua_State *L) {
+ FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *));
+ *pf = NULL; /* file handle is currently `closed' */
+ luaL_getmetatable(L, LUA_FILEHANDLE);
+ lua_setmetatable(L, -2);
+ return pf;
+}
+
+
+/*
+** function to (not) close the standard files stdin, stdout, and stderr
+*/
+static int io_noclose (lua_State *L) {
+ lua_pushnil(L);
+ lua_pushliteral(L, "cannot close standard file");
+ return 2;
+}
+
+
+/*
+** function to close regular files
+*/
+static int io_fclose (lua_State *L) {
+ FILE **p = tofilep(L);
+ int ok = (fclose(*p) == 0);
+ *p = NULL;
+ return pushresult(L, ok, NULL);
+}
+
+
+static int aux_close (lua_State *L) {
+ lua_getfenv(L, 1);
+ lua_getfield(L, -1, "__close");
+ return (lua_tocfunction(L, -1))(L);
+}
+
+
+static int io_close (lua_State *L) {
+ if (lua_isnone(L, 1))
+ lua_rawgeti(L, LUA_ENVIRONINDEX, IO_OUTPUT);
+ tofile(L); /* make sure argument is a file */
+ return aux_close(L);
+}
+
+
+static int io_gc (lua_State *L) {
+ FILE *f = *tofilep(L);
+ /* ignore closed files */
+ if (f != NULL)
+ aux_close(L);
+ return 0;
+}
+
+
+static int io_tostring (lua_State *L) {
+ FILE *f = *tofilep(L);
+ if (f == NULL)
+ lua_pushliteral(L, "file (closed)");
+ else
+ lua_pushfstring(L, "file (%p)", f);
+ return 1;
+}
+
+static int StartsWith(const char *a, const char *b) // this is wolfs being lazy yet again
+{
+ if(strncmp(a, b, strlen(b)) == 0) return 1;
+ return 0;
+}
+
+
+static int io_open (lua_State *L) {
+ FILE **pf;
+ const char *filename = luaL_checkstring(L, 1);
+ int pass = 0;
+ size_t i;
+ int length = strlen(filename);
+ char *splitter, *forward, *backward;
+ char *destFilename;
+ const char *mode = luaL_optstring(L, 2, "r");
+
+ for (i = 0; i < (sizeof (whitelist) / sizeof(const char *)); i++)
+ {
+ if (!stricmp(&filename[length - strlen(whitelist[i])], whitelist[i]))
+ {
+ pass = 1;
+ break;
+ }
+ }
+ if (strstr(filename, "..") || strchr(filename, ':') || StartsWith(filename, "\\")
+ || StartsWith(filename, "/") || !pass)
+ {
+ luaL_error(L,"access denied to %s", filename);
+ return pushresult(L,0,filename);
+ }
+
+ destFilename = va("luafiles"PATHSEP"%s", filename);
+
+ // Make directories as needed
+ splitter = destFilename;
+
+ forward = strchr(splitter, '/');
+ backward = strchr(splitter, '\\');
+ while ((splitter = (forward && backward) ? min(forward, backward) : (forward ?: backward)))
+ {
+ *splitter = 0;
+ I_mkdir(destFilename, 0755);
+ *splitter = '/';
+ splitter++;
+
+ forward = strchr(splitter, '/');
+ backward = strchr(splitter, '\\');
+ }
+
+ pf = newfile(L);
+ *pf = fopen(destFilename, mode);
+ return (*pf == NULL) ? pushresult(L, 0, filename) : 1;
+}
+
+
+static int io_tmpfile (lua_State *L) {
+ FILE **pf = newfile(L);
+ *pf = tmpfile();
+ return (*pf == NULL) ? pushresult(L, 0, NULL) : 1;
+}
+
+
+static FILE *getiofile (lua_State *L, int findex) {
+ FILE *f;
+ lua_rawgeti(L, LUA_ENVIRONINDEX, findex);
+ f = *(FILE **)lua_touserdata(L, -1);
+ if (f == NULL)
+ luaL_error(L, "standard %s file is closed", fnames[findex - 1]);
+ return f;
+}
+
+
+static int g_iofile (lua_State *L, int f, const char *mode) {
+ if (!lua_isnoneornil(L, 1)) {
+ const char *filename = lua_tostring(L, 1);
+ if (filename) {
+ FILE **pf = newfile(L);
+ *pf = fopen(filename, mode);
+ if (*pf == NULL)
+ fileerror(L, 1, filename);
+ }
+ else {
+ tofile(L); /* check that it's a valid file handle */
+ lua_pushvalue(L, 1);
+ }
+ lua_rawseti(L, LUA_ENVIRONINDEX, f);
+ }
+ /* return current value */
+ lua_rawgeti(L, LUA_ENVIRONINDEX, f);
+ return 1;
+}
+
+
+static int io_input (lua_State *L) {
+ return g_iofile(L, IO_INPUT, "r");
+}
+
+
+static int io_output (lua_State *L) {
+ return g_iofile(L, IO_OUTPUT, "w");
+}
+
+
+static int io_readline (lua_State *L);
+
+
+static void aux_lines (lua_State *L, int idx, int toclose) {
+ lua_pushvalue(L, idx);
+ lua_pushboolean(L, toclose); /* close/not close file when finished */
+ lua_pushcclosure(L, io_readline, 2);
+}
+
+
+static int f_lines (lua_State *L) {
+ tofile(L); /* check that it's a valid file handle */
+ aux_lines(L, 1, 0);
+ return 1;
+}
+
+
+static int io_lines (lua_State *L) {
+ if (lua_isnoneornil(L, 1)) { /* no arguments? */
+ /* will iterate over default input */
+ lua_rawgeti(L, LUA_ENVIRONINDEX, IO_INPUT);
+ return f_lines(L);
+ }
+ else {
+ const char *filename = luaL_checkstring(L, 1);
+ FILE **pf = newfile(L);
+ *pf = fopen(filename, "r");
+ if (*pf == NULL)
+ fileerror(L, 1, filename);
+ aux_lines(L, lua_gettop(L), 1);
+ return 1;
+ }
+}
+
+
+/*
+** {======================================================
+** READ
+** =======================================================
+*/
+
+
+static int read_number (lua_State *L, FILE *f) {
+ lua_Number d;
+ if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) {
+ lua_pushnumber(L, d);
+ return 1;
+ }
+ else return 0; /* read fails */
+}
+
+
+static int test_eof (lua_State *L, FILE *f) {
+ int c = getc(f);
+ ungetc(c, f);
+ lua_pushlstring(L, NULL, 0);
+ return (c != EOF);
+}
+
+
+static int read_line (lua_State *L, FILE *f) {
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ for (;;) {
+ size_t l;
+ char *p = luaL_prepbuffer(&b);
+ if (fgets(p, LUAL_BUFFERSIZE, f) == NULL) { /* eof? */
+ luaL_pushresult(&b); /* close buffer */
+ return (lua_objlen(L, -1) > 0); /* check whether read something */
+ }
+ l = strlen(p);
+ if (l == 0 || p[l-1] != '\n')
+ luaL_addsize(&b, l);
+ else {
+ luaL_addsize(&b, l - 1); /* do not include `eol' */
+ luaL_pushresult(&b); /* close buffer */
+ return 1; /* read at least an `eol' */
+ }
+ }
+}
+
+
+static int read_chars (lua_State *L, FILE *f, size_t n) {
+ size_t rlen; /* how much to read */
+ size_t nr; /* number of chars actually read */
+ luaL_Buffer b;
+ luaL_buffinit(L, &b);
+ rlen = LUAL_BUFFERSIZE; /* try to read that much each time */
+ do {
+ char *p = luaL_prepbuffer(&b);
+ if (rlen > n) rlen = n; /* cannot read more than asked */
+ nr = fread(p, sizeof(char), rlen, f);
+ luaL_addsize(&b, nr);
+ n -= nr; /* still have to read `n' chars */
+ } while (n > 0 && nr == rlen); /* until end of count or eof */
+ luaL_pushresult(&b); /* close buffer */
+ return (n == 0 || lua_objlen(L, -1) > 0);
+}
+
+
+static int g_read (lua_State *L, FILE *f, int first) {
+ int nargs = lua_gettop(L) - 1;
+ int success;
+ int n;
+ clearerr(f);
+ if (nargs == 0) { /* no arguments? */
+ success = read_line(L, f);
+ n = first+1; /* to return 1 result */
+ }
+ else { /* ensure stack space for all results and for auxlib's buffer */
+ luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments");
+ success = 1;
+ for (n = first; nargs-- && success; n++) {
+ if (lua_type(L, n) == LUA_TNUMBER) {
+ size_t l = (size_t)lua_tointeger(L, n);
+ success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);
+ }
+ else {
+ const char *p = lua_tostring(L, n);
+ luaL_argcheck(L, p && p[0] == '*', n, "invalid option");
+ switch (p[1]) {
+ case 'n': /* number */
+ success = read_number(L, f);
+ break;
+ case 'l': /* line */
+ success = read_line(L, f);
+ break;
+ case 'a': /* file */
+ read_chars(L, f, ~((size_t)0)); /* read MAX_SIZE_T chars */
+ success = 1; /* always success */
+ break;
+ default:
+ return luaL_argerror(L, n, "invalid format");
+ }
+ }
+ }
+ }
+ if (ferror(f))
+ return pushresult(L, 0, NULL);
+ if (!success) {
+ lua_pop(L, 1); /* remove last result */
+ lua_pushnil(L); /* push nil instead */
+ }
+ return n - first;
+}
+
+
+static int io_read (lua_State *L) {
+ return g_read(L, getiofile(L, IO_INPUT), 1);
+}
+
+
+static int f_read (lua_State *L) {
+ return g_read(L, tofile(L), 2);
+}
+
+
+static int io_readline (lua_State *L) {
+ FILE *f = *(FILE **)lua_touserdata(L, lua_upvalueindex(1));
+ int sucess;
+ if (f == NULL) /* file is already closed? */
+ luaL_error(L, "file is already closed");
+ sucess = read_line(L, f);
+ if (ferror(f))
+ return luaL_error(L, "%s", strerror(errno));
+ if (sucess) return 1;
+ else { /* EOF */
+ if (lua_toboolean(L, lua_upvalueindex(2))) { /* generator created file? */
+ lua_settop(L, 0);
+ lua_pushvalue(L, lua_upvalueindex(1));
+ aux_close(L); /* close it */
+ }
+ return 0;
+ }
+}
+
+/* }====================================================== */
+
+
+static int g_write (lua_State *L, FILE *f, int arg) {
+ int nargs = lua_gettop(L) - 1;
+ int status = 1;
+ size_t count;
+ for (; nargs--; arg++) {
+ if (lua_type(L, arg) == LUA_TNUMBER) {
+ /* optimization: could be done exactly as for strings */
+ status = status &&
+ fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0;
+ }
+ else {
+ size_t l;
+ const char *s = luaL_checklstring(L, arg, &l);
+ count += l;
+ if (ftell(f) + l > FILELIMIT)
+ {
+ luaL_error(L,"write limit bypassed in file. Changes have been discarded.");
+ break;
+ }
+ status = status && (fwrite(s, sizeof(char), l, f) == l);
+ }
+ }
+ return pushresult(L, status, NULL);
+}
+
+
+static int io_write (lua_State *L) {
+ return g_write(L, getiofile(L, IO_OUTPUT), 1);
+}
+
+
+static int f_write (lua_State *L) {
+ return g_write(L, tofile(L), 2);
+}
+
+
+static int f_seek (lua_State *L) {
+ static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END};
+ static const char *const modenames[] = {"set", "cur", "end", NULL};
+ FILE *f = tofile(L);
+ int op = luaL_checkoption(L, 2, "cur", modenames);
+ long offset = luaL_optlong(L, 3, 0);
+ op = fseek(f, offset, mode[op]);
+ if (op)
+ return pushresult(L, 0, NULL); /* error */
+ else {
+ lua_pushinteger(L, ftell(f));
+ return 1;
+ }
+}
+
+
+static int f_setvbuf (lua_State *L) {
+ static const int mode[] = {_IONBF, _IOFBF, _IOLBF};
+ static const char *const modenames[] = {"no", "full", "line", NULL};
+ FILE *f = tofile(L);
+ int op = luaL_checkoption(L, 2, NULL, modenames);
+ lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE);
+ int res = setvbuf(f, NULL, mode[op], sz);
+ return pushresult(L, res == 0, NULL);
+}
+
+
+
+static int io_flush (lua_State *L) {
+ return pushresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL);
+}
+
+
+static int f_flush (lua_State *L) {
+ return pushresult(L, fflush(tofile(L)) == 0, NULL);
+}
+
+
+static const luaL_Reg iolib[] = {
+ {"close", io_close},
+ {"flush", io_flush},
+ {"input", io_input},
+ {"lines", io_lines},
+ {"open", io_open},
+ {"output", io_output},
+ {"read", io_read},
+ {"tmpfile", io_tmpfile},
+ {"type", io_type},
+ {"write", io_write},
+ {NULL, NULL}
+};
+
+
+static const luaL_Reg flib[] = {
+ {"close", io_close},
+ {"flush", f_flush},
+ {"lines", f_lines},
+ {"read", f_read},
+ {"seek", f_seek},
+ {"setvbuf", f_setvbuf},
+ {"write", f_write},
+ {"__gc", io_gc},
+ {"__tostring", io_tostring},
+ {NULL, NULL}
+};
+
+
+static void createmeta (lua_State *L) {
+ luaL_newmetatable(L, LUA_FILEHANDLE); /* create metatable for file handles */
+ lua_pushvalue(L, -1); /* push metatable */
+ lua_setfield(L, -2, "__index"); /* metatable.__index = metatable */
+ luaL_register(L, NULL, flib); /* file methods */
+}
+
+
+static void createstdfile (lua_State *L, FILE *f, int k, const char *fname) {
+ *newfile(L) = f;
+ if (k > 0) {
+ lua_pushvalue(L, -1);
+ lua_rawseti(L, LUA_ENVIRONINDEX, k);
+ }
+ lua_pushvalue(L, -2); /* copy environment */
+ lua_setfenv(L, -2); /* set it */
+ lua_setfield(L, -3, fname);
+}
+
+
+static void newfenv (lua_State *L, lua_CFunction cls) {
+ lua_createtable(L, 0, 1);
+ lua_pushcfunction(L, cls);
+ lua_setfield(L, -2, "__close");
+}
+
+
+LUALIB_API int luaopen_io (lua_State *L) {
+ createmeta(L);
+ /* create (private) environment (with fields IO_INPUT, IO_OUTPUT, __close) */
+ newfenv(L, io_fclose);
+ lua_replace(L, LUA_ENVIRONINDEX);
+ /* open library */
+ luaL_register(L, LUA_IOLIBNAME, iolib);
+ /* create (and set) default files */
+ newfenv(L, io_noclose); /* close function for default files */
+ createstdfile(L, stdin, IO_INPUT, "stdin");
+ createstdfile(L, stdout, IO_OUTPUT, "stdout");
+ createstdfile(L, stderr, 0, "stderr");
+ lua_pop(L, 1); /* pop environment for default files */
+ return 1;
+}
+
diff --git a/src/blua/lualib.h b/src/blua/lualib.h
index 6ebe27287..4ea97edf3 100644
--- a/src/blua/lualib.h
+++ b/src/blua/lualib.h
@@ -21,6 +21,9 @@ LUALIB_API int (luaopen_base) (lua_State *L);
#define LUA_TABLIBNAME "table"
LUALIB_API int (luaopen_table) (lua_State *L);
+#define LUA_IOLIBNAME "io"
+LUALIB_API int (luaopen_io) (lua_State *L);
+
#define LUA_STRLIBNAME "string"
LUALIB_API int (luaopen_string) (lua_State *L);
diff --git a/src/config.h.in b/src/config.h.in
index a7a0be53b..b529121e1 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -37,7 +37,7 @@
* Last updated 2015 / 05 / 03 - SRB2 v2.1.15 - srb2.srb
* Last updated 2018 / 12 / 23 - SRB2 v2.1.22 - patch.dta
* 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
*/
// Base SRB2 hashes
@@ -52,7 +52,7 @@
#define ASSET_HASH_CHARS_KART "e2c428347dde52858a3dacd29fc5b964"
#define ASSET_HASH_MAPS_KART "1335cd064656aedca359cfbb5233ac4a"
#ifdef USE_PATCH_KART
-#define ASSET_HASH_PATCH_KART "2a556446ab428492110e7544841e1f42"
+#define ASSET_HASH_PATCH_KART "f7b68076f8abc54e1a78963d97f69ab2"
#endif
#endif
diff --git a/src/console.c b/src/console.c
index 5c4a2fc8a..0449ced69 100644
--- a/src/console.c
+++ b/src/console.c
@@ -31,6 +31,7 @@
#include "i_video.h"
#include "z_zone.h"
#include "i_system.h"
+#include "i_threads.h"
#include "d_main.h"
#include "m_menu.h"
#include "filesrch.h"
@@ -45,6 +46,16 @@
#define MAXHUDLINES 20
+#ifdef HAVE_THREADS
+I_mutex con_mutex;
+
+# define Lock_state() I_lock_mutex(&con_mutex)
+# define Unlock_state() I_unlock_mutex(con_mutex)
+#else/*HAVE_THREADS*/
+# define Lock_state()
+# define Unlock_state()
+#endif/*HAVE_THREADS*/
+
static boolean con_started = false; // console has been initialised
boolean con_startup = false; // true at game startup, screen need refreshing
static boolean con_forcepic = true; // at startup toggle console translucency when first off
@@ -170,6 +181,8 @@ static void CONS_hudlines_Change(void)
{
INT32 i;
+ Lock_state();
+
// Clear the currently displayed lines
for (i = 0; i < con_hudlines; i++)
con_hudtime[i] = 0;
@@ -181,6 +194,8 @@ static void CONS_hudlines_Change(void)
con_hudlines = cons_hudlines.value;
+ Unlock_state();
+
CONS_Printf(M_GetText("Number of console HUD lines is now %d\n"), con_hudlines);
}
@@ -188,12 +203,16 @@ static void CONS_hudlines_Change(void)
//
static void CONS_Clear_f(void)
{
+ Lock_state();
+
memset(con_buffer, 0, CON_BUFFERSIZE);
con_cx = 0;
con_cy = con_totallines-1;
con_line = &con_buffer[con_cy*con_width];
con_scrollup = 0;
+
+ Unlock_state();
}
// Choose english keymap
@@ -369,20 +388,29 @@ void CON_Init(void)
for (i = 0; i < NUMINPUTS; i++)
bindtable[i] = NULL;
+ Lock_state();
+
// clear all lines
memset(con_buffer, 0, CON_BUFFERSIZE);
// make sure it is ready for the loading screen
con_width = 0;
+
+ Unlock_state();
+
CON_RecalcSize();
CON_SetupColormaps();
+ Lock_state();
+
//note: CON_Ticker should always execute at least once before D_Display()
con_clipviewtop = -1; // -1 does not clip
con_hudlines = atoi(cons_hudlines.defaultvalue);
+ Unlock_state();
+
// setup console input filtering
CON_InputInit();
@@ -391,15 +419,23 @@ void CON_Init(void)
COM_AddCommand("cls", CONS_Clear_f);
//COM_AddCommand("english", CONS_English_f);
// set console full screen for game startup MAKE SURE VID_Init() done !!!
+ Lock_state();
+
con_destlines = vid.height;
con_curlines = vid.height;
+ Unlock_state();
if (!dedicated)
{
+ Lock_state();
+
con_started = true;
con_startup = true; // need explicit screen refresh until we are in Doom loop
consoletoggle = false;
+
+ Unlock_state();
+
CV_RegisterVar(&cons_msgtimeout);
CV_RegisterVar(&cons_hudlines);
CV_RegisterVar(&cons_speed);
@@ -411,19 +447,27 @@ void CON_Init(void)
}
else
{
+ Lock_state();
+
con_started = true;
con_startup = false; // need explicit screen refresh until we are in Doom loop
consoletoggle = true;
+
+ Unlock_state();
}
}
// Console input initialization
//
static void CON_InputInit(void)
{
+ Lock_state();
+
// prepare the first prompt line
memset(inputlines, 0, sizeof (inputlines));
inputline = 0;
input_cur = input_sel = input_len = 0;
+
+ Unlock_state();
}
//======================================================================
@@ -439,6 +483,8 @@ static void CON_RecalcSize(void)
char *tmp_buffer;
char *string;
+ Lock_state();
+
switch (cv_constextsize.value)
{
case V_NOSCALEPATCH:
@@ -476,11 +522,18 @@ static void CON_RecalcSize(void)
// check for change of video width
if (conw == con_width)
+ {
+ Unlock_state();
return; // didn't change
+ }
+
+ Unlock_state();
tmp_buffer = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL);
string = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); // BP: it is a line but who know
+ Lock_state();
+
oldcon_width = con_width;
oldnumlines = con_totallines;
oldcon_cy = con_cy;
@@ -501,6 +554,8 @@ static void CON_RecalcSize(void)
con_line = &con_buffer[con_cy*con_width];
con_scrollup = 0;
+ Unlock_state();
+
// re-arrange console text buffer to keep text
if (oldcon_width) // not the first time
{
@@ -525,7 +580,11 @@ static void CON_RecalcSize(void)
static void CON_ChangeHeight(void)
{
- INT32 minheight = 20 * con_scalefactor; // 20 = 8+8+4
+ INT32 minheight;
+
+ Lock_state();
+
+ minheight = 20 * con_scalefactor; // 20 = 8+8+4
// toggle console in
con_destlines = (cons_height.value*vid.height)/100;
@@ -535,13 +594,19 @@ static void CON_ChangeHeight(void)
con_destlines = vid.height;
con_destlines &= ~0x3; // multiple of text row height
+
+ Unlock_state();
}
// Handles Console moves in/out of screen (per frame)
//
static void CON_MoveConsole(void)
{
- const fixed_t conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT);
+ fixed_t conspeed;
+
+ Lock_state();
+
+ conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT);
// instant
if (!cons_speed.value)
@@ -563,6 +628,8 @@ static void CON_MoveConsole(void)
if (con_curlines < con_destlines)
con_curlines = con_destlines;
}
+
+ Unlock_state();
}
INT32 CON_ShiftChar(INT32 ch)
@@ -608,27 +675,44 @@ void CON_ClearHUD(void)
{
INT32 i;
+ Lock_state();
+
for (i = 0; i < con_hudlines; i++)
con_hudtime[i] = 0;
+
+ Unlock_state();
}
// Force console to move out immediately
// note: con_ticker will set consoleready false
void CON_ToggleOff(void)
{
+ Lock_state();
+
if (!con_destlines)
+ {
+ Unlock_state();
return;
+ }
con_destlines = 0;
con_curlines = 0;
CON_ClearHUD();
con_forcepic = 0;
con_clipviewtop = -1; // remove console clipping of view
+
+ Unlock_state();
}
boolean CON_Ready(void)
{
- return consoleready;
+ boolean ready;
+ Lock_state();
+ {
+ ready = consoleready;
+ }
+ Unlock_state();
+ return ready;
}
// Console ticker: handles console move in/out, cursor blinking
@@ -636,7 +720,11 @@ boolean CON_Ready(void)
void CON_Ticker(void)
{
INT32 i;
- INT32 minheight = 20 * con_scalefactor; // 20 = 8+8+4
+ INT32 minheight;
+
+ Lock_state();
+
+ minheight = 20 * con_scalefactor; // 20 = 8+8+4
// cursor blinking
con_tick++;
@@ -694,6 +782,8 @@ void CON_Ticker(void)
if (con_hudtime[i] < 0)
con_hudtime[i] = 0;
}
+
+ Unlock_state();
}
//
@@ -705,32 +795,51 @@ void CON_Ticker(void)
static void CON_InputClear(void)
{
+ Lock_state();
+
memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
input_cur = input_sel = input_len = 0;
+
+ Unlock_state();
}
static void CON_InputSetString(const char *c)
{
+ Lock_state();
+
memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS);
strcpy(inputlines[inputline], c);
input_cur = input_sel = input_len = strlen(c);
+
+ Unlock_state();
}
static void CON_InputAddString(const char *c)
{
size_t csize = strlen(c);
+
+ Lock_state();
+
if (input_len + csize > CON_MAXPROMPTCHARS-1)
+ {
+ Unlock_state();
return;
+ }
if (input_cur != input_len)
memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur);
memcpy(&inputlines[inputline][input_cur], c, csize);
input_len += csize;
input_sel = (input_cur += csize);
+
+ Unlock_state();
}
static void CON_InputDelSelection(void)
{
size_t start, end, len;
+
+ Lock_state();
+
if (input_cur > input_sel)
{
start = input_sel;
@@ -749,27 +858,39 @@ static void CON_InputDelSelection(void)
input_len -= len;
input_sel = input_cur = start;
+
+ Unlock_state();
}
static void CON_InputAddChar(char c)
{
if (input_len >= CON_MAXPROMPTCHARS-1)
return;
+
+ Lock_state();
+
if (input_cur != input_len)
memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur);
inputlines[inputline][input_cur++] = c;
inputlines[inputline][++input_len] = 0;
input_sel = input_cur;
+
+ Unlock_state();
}
static void CON_InputDelChar(void)
{
if (!input_cur)
return;
+
+ Lock_state();
+
if (input_cur != input_len)
memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur);
inputlines[inputline][--input_len] = 0;
input_sel = --input_cur;
+
+ Unlock_state();
}
//
@@ -1208,6 +1329,8 @@ static void CON_Print(char *msg)
S_StartSound(NULL, sfx_radio);
}
+ Lock_state();
+
if (!(*msg & 0x80))
{
con_line[con_cx++] = '\x80';
@@ -1268,7 +1391,10 @@ static void CON_Print(char *msg)
}
if (*msg == '\0')
+ {
+ Unlock_state();
return;
+ }
// printable character
for (l = 0; l < (con_width-11) && msg[l] > ' '; l++)
@@ -1286,6 +1412,8 @@ static void CON_Print(char *msg)
for (; l > 0; l--)
con_line[con_cx++] = *(msg++);
}
+
+ Unlock_state();
}
void CON_LogMessage(const char *msg)
@@ -1317,6 +1445,7 @@ void CONS_Printf(const char *fmt, ...)
{
va_list argptr;
static char *txt = NULL;
+ boolean startup;
if (txt == NULL)
txt = malloc(8192);
@@ -1349,11 +1478,16 @@ void CONS_Printf(const char *fmt, ...)
CON_LogMessage(txt);
#endif
+ Lock_state();
+
// make sure new text is visible
con_scrollup = 0;
+ startup = con_startup;
+
+ Unlock_state();
// if not in display loop, force screen update
- if (con_startup)
+ if (startup)
{
#if (defined (_WINDOWS)) || (defined (__OS2__) && !defined (HAVE_SDL))
patch_t *con_backpic = W_CachePatchName("KARTKREW", PU_CACHE);
@@ -1667,8 +1801,13 @@ static void CON_DrawConsole(void)
//
void CON_Drawer(void)
{
+ Lock_state();
+
if (!con_started || !graphics_started)
+ {
+ Unlock_state();
return;
+ }
if (con_recalc)
CON_RecalcSize();
@@ -1678,4 +1817,6 @@ void CON_Drawer(void)
else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS
|| gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS)
CON_DrawHudlines();
+
+ Unlock_state();
}
diff --git a/src/console.h b/src/console.h
index 53d1787f6..46bc57017 100644
--- a/src/console.h
+++ b/src/console.h
@@ -12,6 +12,7 @@
#include "d_event.h"
#include "command.h"
+#include "i_threads.h"
#ifdef _WII
void CON_InitWii(void);
@@ -21,6 +22,10 @@ void CON_Init(void);
boolean CON_Responder(event_t *ev);
+#ifdef HAVE_THREADS
+extern I_mutex con_mutex;
+#endif
+
// set true when screen size has changed, to adapt console
extern boolean con_recalc;
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 652ffbcb8..88fdb35a8 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -46,6 +46,7 @@
#include "lua_script.h"
#include "lua_hook.h"
#include "k_kart.h"
+#include "s_sound.h" // sfx_syfail
#ifdef CLIENT_LOADINGSCREEN
// cl loading screen
@@ -57,6 +58,10 @@
#include "sdl12/SRB2XBOX/xboxhelp.h"
#endif
+#ifdef HAVE_DISCORDRPC
+#include "discord.h"
+#endif
+
//
// NETWORKING
//
@@ -76,7 +81,7 @@
boolean server = true; // true or false but !server == client
#define client (!server)
boolean nodownload = false;
-static boolean serverrunning = false;
+boolean serverrunning = false;
INT32 serverplayer = 0;
char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
@@ -141,6 +146,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
@@ -1093,8 +1101,10 @@ static INT16 Consistancy(void);
typedef enum
{
CL_SEARCHING,
+ CL_CHECKFILES,
CL_DOWNLOADFILES,
CL_ASKJOIN,
+ CL_LOADFILES,
CL_WAITJOINRESPONSE,
#ifdef JOININGAME
CL_DOWNLOADSAVEGAME,
@@ -1102,6 +1112,7 @@ typedef enum
CL_CONNECTED,
CL_ABORTED,
CL_ASKFULLFILELIST,
+ CL_CONFIRMCONNECT,
#ifdef HAVE_CURL
CL_PREPAREHTTPFILES,
CL_DOWNLOADHTTPFILES,
@@ -1166,11 +1177,7 @@ static inline void CL_DrawConnectionStatus(void)
// Draw background fade
V_DrawFadeScreen(0xFF00, 16);
- // Draw the 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");
-
- if (cl_mode != CL_DOWNLOADFILES
+ if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_LOADFILES
#ifdef HAVE_CURL
&& cl_mode != CL_DOWNLOADHTTPFILES
#endif
@@ -1181,6 +1188,10 @@ static inline void CL_DrawConnectionStatus(void)
// 15 pal entries total.
const char *cltext;
+ //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));
@@ -1202,11 +1213,22 @@ static inline void CL_DrawConnectionStatus(void)
break;
#endif
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:
@@ -1221,19 +1243,47 @@ 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-24, 256, 8, 175);
- V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 160);
+ V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, 256, 8, 175);
+ V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, dldlength, 8, 160);
memset(tempname, 0, sizeof(tempname));
// offset filename to just the name only part
@@ -1251,16 +1301,52 @@ static inline void CL_DrawConnectionStatus(void)
strncpy(tempname, filename, sizeof(tempname)-1);
}
- V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
+ V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-22, V_YELLOWMAP,
va(M_GetText("Downloading \"%s\""), tempname));
- V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 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-24, 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
+ {
+ 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..."));
+ }
}
}
#endif
@@ -1270,7 +1356,7 @@ static boolean CL_AskFileList(INT32 firstfile)
netbuffer->packettype = PT_TELLFILESNEEDED;
netbuffer->u.filesneedednum = firstfile;
- return HSendPacket(servernode, true, 0, sizeof (INT32));
+ return HSendPacket(servernode, false, 0, sizeof (INT32));
}
/** Sends a special packet to declare how many players in local
@@ -1306,13 +1392,75 @@ static boolean CL_SendJoin(void)
return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak));
}
+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;
@@ -1336,8 +1484,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);
@@ -1378,7 +1525,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl, mapheaderinfo[gamemap-1]->actnum) < 0)
{
// If there's an encoding error, send UNKNOWN, we accept that the above may be truncated
- strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
+ strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", 33);
}
}
else
@@ -1389,19 +1536,18 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl) < 0)
{
// If there's an encoding error, send UNKNOWN, we accept that the above may be truncated
- strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
+ strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", 33);
}
}
}
}
else
- strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
+ strncpy(netbuffer->u.serverinfo.maptitle, "Unknown", 33);
netbuffer->u.serverinfo.maptitle[32] = '\0';
netbuffer->u.serverinfo.actnum = 0; //mapheaderinfo[gamemap-1]->actnum
-#ifdef HAVE_CURL
mirror_length = strlen(httpurl);
if (mirror_length > MAX_MIRROR_LENGTH)
mirror_length = MAX_MIRROR_LENGTH;
@@ -1411,7 +1557,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);
@@ -1524,6 +1669,10 @@ static boolean SV_SendServerConfig(INT32 node)
netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor;
}
+ netbuffer->u.servercfg.maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value));
+ netbuffer->u.servercfg.allownewplayer = cv_allownewplayer.value;
+ netbuffer->u.servercfg.discordinvites = (boolean)cv_discordinvites.value;
+
memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
op = p = netbuffer->u.servercfg.varlengthinputs;
@@ -1721,7 +1870,7 @@ static void CL_LoadReceivedSavegame(void)
if (strlen(mapheaderinfo[gamemap-1]->zonttl) > 0)
CON_LogMessage(va(" %s", mapheaderinfo[gamemap-1]->zonttl));
else if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
- CON_LogMessage(M_GetText(" ZONE"));
+ CON_LogMessage(M_GetText(" Zone"));
if (strlen(mapheaderinfo[gamemap-1]->actnum) > 0)
CON_LogMessage(va(" %s", mapheaderinfo[gamemap-1]->actnum));
}
@@ -1748,7 +1897,7 @@ static void CL_LoadReceivedSavegame(void)
#endif
#ifndef NONET
-static void SendAskInfo(INT32 node, boolean viams)
+static void SendAskInfo(INT32 node)
{
const tic_t asktime = I_GetTime();
netbuffer->packettype = PT_ASKINFO;
@@ -1759,10 +1908,6 @@ static void SendAskInfo(INT32 node, boolean viams)
// now allowed traffic from the host to us in, so once the MS relays
// our address to the host, it'll be able to speak to us.
HSendPacket(node, false, 0, sizeof (askinfo_pak));
-
- // Also speak to the MS.
- if (viams && node != 0 && node != BROADCASTADDR)
- SendAskInfoViaMS(node, asktime);
}
serverelem_t serverlist[MAXSERVERLIST];
@@ -1828,13 +1973,12 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
M_SortServerList();
}
-void CL_UpdateServerList(boolean internetsearch, INT32 room)
+void CL_UpdateServerList (void)
{
SL_ClearServerList(0);
if (!netgame && I_NetOpenSocket)
{
- MSCloseUDPSocket(); // Tidy up before wiping the slate.
if (I_NetOpenSocket())
{
netgame = true;
@@ -1844,67 +1988,95 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room)
// search for local servers
if (netgame)
- SendAskInfo(BROADCASTADDR, false);
+ SendAskInfo(BROADCASTADDR);
+}
- if (internetsearch)
+void CL_QueryServerList (msg_server_t *server_list)
+{
+ INT32 i;
+
+ CL_UpdateServerList();
+
+ for (i = 0; server_list[i].header.buffer[0]; i++)
{
- const msg_server_t *server_list;
- INT32 i = -1;
- server_list = GetShortServersList(room);
- if (server_list)
+ // Make sure MS version matches our own, to
+ // thwart nefarious servers who lie to the MS.
+
+ /* lol bruh, that version COMES from the servers */
+ //if (strcmp(version, server_list[i].version) == 0)
{
- char version[8] = "";
-#if VERSION > 0 || SUBVERSION > 0
- snprintf(version, sizeof (version), "%d.%d", VERSION, SUBVERSION);
-#else
- strcpy(version, GetRevisionString());
-#endif
- version[sizeof (version) - 1] = '\0';
+ INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
+ if (node == -1)
+ break; // no more node free
+ SendAskInfo(node);
+ // Force close the connection so that servers can't eat
+ // up nodes forever if we never get a reply back from them
+ // (usually when they've not forwarded their ports).
+ //
+ // Don't worry, we'll get in contact with the working
+ // servers again when they send SERVERINFO to us later!
+ //
+ // (Note: as a side effect this probably means every
+ // server in the list will probably be using the same node (e.g. node 1),
+ // not that it matters which nodes they use when
+ // the connections are closed afterwards anyway)
+ // -- Monster Iestyn 12/11/18
+ Net_CloseConnection(node|FORCECLOSE);
+ }
+ }
+}
+#endif // ifndef NONET
- for (i = 0; server_list[i].header.buffer[0]; i++)
+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[gc_accelerate][0] || ev->data1 == gamecontrol[gc_accelerate][1])
+ {
+ if (totalfilesrequestednum > 0)
{
- // Make sure MS version matches our own, to
- // thwart nefarious servers who lie to the MS.
-
- if (strcmp(version, server_list[i].version) == 0)
+#ifdef HAVE_CURL
+ if (http_source[0] == '\0' || curl_failedwebdownload)
+#endif
{
- INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
- if (node == -1)
- break; // no more node free
- SendAskInfo(node, true);
- // Force close the connection so that servers can't eat
- // up nodes forever if we never get a reply back from them
- // (usually when they've not forwarded their ports).
- //
- // Don't worry, we'll get in contact with the working
- // servers again when they send SERVERINFO to us later!
- //
- // (Note: as a side effect this probably means every
- // server in the list will probably be using the same node (e.g. node 1),
- // not that it matters which nodes they use when
- // the connections are closed afterwards anyway)
- // -- Monster Iestyn 12/11/18
- Net_CloseConnection(node|FORCECLOSE);
+ if (CL_SendRequestFile())
+ {
+ cl_mode = CL_DOWNLOADFILES;
+ }
}
+#ifdef HAVE_CURL
+ else
+ cl_mode = CL_PREPAREHTTPFILES;
+#endif
}
+ else
+ cl_mode = CL_LOADFILES;
+
+ M_ClearMenus(true);
}
-
- //no server list?(-1) or no servers?(0)
- if (!i)
+ else if (ev->data1 == 'n' || ev->data1 == KEY_ESCAPE|| ev->data1 == gamecontrol[gc_brake][0] || ev->data1 == gamecontrol[gc_brake][1])
{
- ; /// TODO: display error or warning?
+ cl_mode = CL_ABORTED;
+ M_ClearMenus(true);
}
}
+#else
+ (void)ev;
+#endif
}
-#endif // ifndef NONET
-
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();
@@ -1923,17 +2095,31 @@ static boolean CL_FinishedFileList(void)
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
- "You have WAD files loaded or have\n"
- "modified the game in some way, and\n"
- "your file list does not match\n"
- "the server's file list.\n"
- "Please restart SRB2Kart before connecting.\n\n"
+ "You have the wrong addons loaded.\n\n"
+ "To play on this server, restart\n"
+ "the game and don't load any addons.\n"
+ "SRB2Kart will automatically add\n"
+ "everything you need when you join.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
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
@@ -1948,23 +2134,71 @@ static boolean CL_FinishedFileList(void)
CL_Reset();
D_StartTitle();
M_StartMessage(M_GetText(
- "You cannot connect to this server\n"
- "because you cannot download the files\n"
- "that you are missing from the server.\n\n"
- "See the console or log file for\n"
- "more details.\n\n"
+ "An error occured when trying to\n"
+ "download missing addons.\n"
+ "(This is almost always a problem\n"
+ "with the server, not your game.)\n\n"
+ "See the console or log file\n"
+ "for additional details.\n\n"
"Press ESC\n"
), NULL, MM_NOTHING);
return false;
}
+ }
- if (CL_SendRequestFile())
- 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_SendRequestFile())
+ {
+ cl_mode = CL_DOWNLOADFILES;
+ }
}
#endif
}
@@ -1973,14 +2207,13 @@ static boolean CL_FinishedFileList(void)
/** Called by CL_ServerConnectionTicker
*
- * \param viams ???
* \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit.
* \return False if the connection was aborted
* \sa CL_ServerConnectionTicker
* \sa CL_ConnectToServer
*
*/
-static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
+static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
{
#ifndef NONET
INT32 i;
@@ -2005,11 +2238,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
// Quit here rather than downloading files and being refused later.
if (serverlist[i].info.numberofplayer >= serverlist[i].info.maxplayer)
{
- D_QuitNetGame();
- CL_Reset();
- D_StartTitle();
- M_StartMessage(va(M_GetText("Maximum players reached: %d\n\nPress ESC\n"), serverlist[i].info.maxplayer), NULL, MM_NOTHING);
- return false;
+ serverisfull = true;
}
if (client)
@@ -2023,7 +2252,6 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, 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)
{
@@ -2032,8 +2260,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, 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.
@@ -2042,13 +2269,12 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
}
// Ask the info to the server (askinfo packet)
- if (*asksent + NEWTICRATE < I_GetTime())
+ if (I_GetTime() >= *asksent)
{
- SendAskInfo(servernode, viams);
- *asksent = I_GetTime();
+ SendAskInfo(servernode);
+ *asksent = I_GetTime() + NEWTICRATE;
}
#else
- (void)viams;
(void)asksent;
// No netgames, so we skip this state.
cl_mode = CL_ASKJOIN;
@@ -2059,7 +2285,6 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
/** Called by CL_ConnectToServer
*
- * \param viams ???
* \param tmpsave The name of the gamestate file???
* \param oldtic Used for knowing when to poll events and redraw
* \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit.
@@ -2068,11 +2293,11 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
* \sa CL_ConnectToServer
*
*/
-static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic_t *oldtic, tic_t *asksent)
+static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent)
{
boolean waitmore;
INT32 i;
-
+
#ifdef NONET
(void)tmpsave;
#endif
@@ -2080,34 +2305,36 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
switch (cl_mode)
{
case CL_SEARCHING:
- if (!CL_ServerConnectionSearchTicker(viams, asksent))
+ if (!CL_ServerConnectionSearchTicker(asksent))
return false;
break;
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;
@@ -2131,19 +2358,13 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, 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_SendRequestFile())
- {
- 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
@@ -2159,21 +2380,50 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, 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();
+ 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;
+ }
#ifdef JOININGAME
// 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;
-
#ifdef JOININGAME
case CL_DOWNLOADSAVEGAME:
// At this state, the first (and only) needed file is the gamestate
@@ -2182,13 +2432,13 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, 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;
@@ -2209,9 +2459,13 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
INT32 key;
I_OsPolling();
+
+ if (cl_mode == CL_CONFIRMCONNECT)
+ D_ProcessEvents(); //needed for menu system to receive inputs
+
key = I_GetKey();
// Only ESC and non-keyboard keys abort connection
- if (key == KEY_ESCAPE || key >= KEY_MOUSE1)
+ if (key == KEY_ESCAPE || key >= KEY_MOUSE1 || cl_mode == CL_ABORTED)
{
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
D_QuitNetGame();
@@ -2228,6 +2482,13 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
F_TitleScreenTicker(true);
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();
@@ -2245,11 +2506,10 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
/** Use adaptive send using net_bandwidth and stat.sendbytes
*
- * \param viams ???
* \todo Better description...
*
*/
-static void CL_ConnectToServer(boolean viams)
+static void CL_ConnectToServer(void)
{
INT32 pnumnodes, nodewaited = doomcom->numnodes, i;
tic_t oldtic;
@@ -2293,9 +2553,10 @@ static void CL_ConnectToServer(boolean viams)
ClearAdminPlayers();
pnumnodes = 1;
- oldtic = I_GetTime() - 1;
+ oldtic = 0;
#ifndef NONET
- asksent = (tic_t) - TICRATE;
+ asksent = 0;
+ firstconnectattempttime = I_GetTime();
i = SL_SearchServer(servernode);
@@ -2321,9 +2582,9 @@ static void CL_ConnectToServer(boolean viams)
{
// If the connection was aborted for some reason, leave
#ifndef NONET
- if (!CL_ServerConnectionTicker(viams, tmpsave, &oldtic, &asksent))
+ if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent))
#else
- if (!CL_ServerConnectionTicker(viams, (char*)NULL, &oldtic, (tic_t *)NULL))
+ if (!CL_ServerConnectionTicker((char*)NULL, &oldtic, (tic_t *)NULL))
#endif
return;
@@ -2504,9 +2765,6 @@ static void Command_ReloadBan(void) //recheck ban.txt
static void Command_connect(void)
{
- // Assume we connect directly.
- boolean viams = false;
-
if (COM_Argc() < 2 || *COM_Argv(1) == 0)
{
CONS_Printf(M_GetText(
@@ -2540,9 +2798,6 @@ static void Command_connect(void)
if (netgame && !stricmp(COM_Argv(1), "node"))
{
servernode = (SINT8)atoi(COM_Argv(2));
-
- // Use MS to traverse NAT firewalls.
- viams = true;
}
else if (netgame)
{
@@ -2551,7 +2806,6 @@ static void Command_connect(void)
}
else if (I_NetOpenSocket)
{
- MSCloseUDPSocket(); // Tidy up before wiping the slate.
I_NetOpenSocket();
netgame = true;
multiplayer = true;
@@ -2580,7 +2834,7 @@ static void Command_connect(void)
}
botingame = false;
botskin = 0;
- CL_ConnectToServer(viams);
+ CL_ConnectToServer();
}
#endif
@@ -2687,6 +2941,8 @@ void CL_RemovePlayer(INT32 playernum, INT32 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
@@ -2737,6 +2993,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;
@@ -3076,6 +3340,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:
@@ -3203,12 +3472,21 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
consvar_t cv_netticbuffer = {"netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL };
+static void Joinable_OnChange(void);
+
+consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_CALL, CV_OnOff, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
#ifdef VANILLAJOINNEXTROUND
consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
#endif
+
static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}};
-consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE|CV_CALL, maxplayers_cons_t, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+// 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[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
consvar_t cv_resynchattempts = {"resynchattempts", "5", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_blamecfail = {"blamecfail", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL };
@@ -3225,6 +3503,24 @@ consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons
static void Got_AddPlayer(UINT8 **p, INT32 playernum);
static void Got_RemovePlayer(UINT8 **p, INT32 playernum);
+static void Joinable_OnChange(void)
+{
+ 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)
{
@@ -3315,6 +3611,8 @@ void SV_ResetServer(void)
adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
}
+ memset(player_name_changes, 0, sizeof player_name_changes);
+
mynode = 0;
cl_packetmissed = false;
@@ -3377,8 +3675,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])
{
@@ -3474,6 +3774,9 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
if (netgame)
{
+ if (node != mynode)
+ S_StartSound(NULL, sfx_join);
+
if (server && cv_showjoinaddress.value)
{
const char *address;
@@ -3490,6 +3793,10 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
#ifdef HAVE_BLUA
LUAh_PlayerJoin(newplayernum);
#endif
+
+#ifdef HAVE_DISCORDRPC
+ DRPC_UpdatePresence();
+#endif
}
// Xcmd XD_REMOVEPLAYER
@@ -3516,6 +3823,10 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum)
reason = READUINT8(*p);
CL_RemovePlayer(pnum, reason);
+
+#ifdef HAVE_DISCORDRPC
+ DRPC_UpdatePresence();
+#endif
}
static boolean SV_AddWaitingPlayers(void)
@@ -3625,15 +3936,16 @@ boolean SV_SpawnServer(void)
SV_GenContext();
if (netgame && I_NetOpenSocket)
{
- MSCloseUDPSocket(); // Tidy up before wiping the slate.
I_NetOpenSocket();
- if (ms_RoomId > 0)
+#ifdef MASTERSERVER
+ if (cv_advertise.value)
RegisterServer();
+#endif
}
// non dedicated server just connect to itself
if (!dedicated)
- CL_ConnectToServer(false);
+ CL_ConnectToServer();
else doomcom->numslots = 1;
}
@@ -3659,7 +3971,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;
@@ -3685,7 +3997,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);
}
@@ -3836,6 +4148,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);
@@ -3844,6 +4157,8 @@ static void HandleServerInfo(SINT8 node)
netbuffer->u.serverinfo.servername[MAXSERVERNAME-1] = 0;
netbuffer->u.serverinfo.application
[sizeof netbuffer->u.serverinfo.application - 1] = '\0';
+ memcpy(servername, netbuffer->u.serverinfo.servername, MAXSERVERNAME);
+ CopyCaretColors(netbuffer->u.serverinfo.servername, servername, MAXSERVERNAME);
netbuffer->u.serverinfo.gametype = (UINT8)((netbuffer->u.serverinfo.gametype == VANILLA_GT_MATCH) ? GT_MATCH : GT_RACE);
SL_InsertServer(&netbuffer->u.serverinfo, node);
@@ -3957,13 +4272,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.
@@ -3983,7 +4309,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)
@@ -3997,6 +4323,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);
@@ -5346,7 +5678,9 @@ FILESTAMP
GetPackets();
FILESTAMP
+#ifdef MASTERSERVER
MasterClient_Ticker();
+#endif
if (client)
{
@@ -5403,7 +5737,9 @@ FILESTAMP
// 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)
{
@@ -5459,7 +5795,13 @@ FILESTAMP
if (nowtime > resptime)
{
resptime = nowtime;
+#ifdef HAVE_THREADS
+ I_lock_mutex(&m_menu_mutex);
+#endif
M_Ticker();
+#ifdef HAVE_THREADS
+ I_unlock_mutex(m_menu_mutex);
+#endif
CON_Ticker();
}
SV_FileSendTicker();
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index e9b180dfe..e94fd7e83 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -18,6 +18,7 @@
#include "d_netcmd.h"
#include "tables.h"
#include "d_player.h"
+#include "mserv.h"
/*
The 'packet version' is used to distinguish packet formats.
@@ -333,6 +334,11 @@ typedef struct
char server_context[8]; // Unique context id, generated at server startup.
+ // Discord info (always defined for net compatibility)
+ UINT8 maxplayer;
+ boolean allownewplayer;
+ boolean discordinvites;
+
UINT8 varlengthinputs[0]; // Playernames and netvars
} ATTRPACK serverconfig_pak;
@@ -530,7 +536,12 @@ 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)
extern boolean dedicated; // For dedicated server
extern UINT16 software_MAXPACKETLENGTH;
@@ -552,6 +563,8 @@ extern consvar_t
#endif
cv_netticbuffer, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed;
+extern consvar_t cv_discordinvites;
+
// Used in d_net, the only dependence
tic_t ExpandTics(INT32 low, tic_t basetic);
void D_ClientServerInit(void);
@@ -577,7 +590,8 @@ void CL_RemoveSplitscreenPlayer(UINT8 p);
void CL_Reset(void);
void CL_ClearPlayer(INT32 playernum);
void CL_RemovePlayer(INT32 playernum, INT32 reason);
-void CL_UpdateServerList(boolean internetsearch, INT32 room);
+void CL_QueryServerList(msg_server_t *list);
+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 85b363e0e..0404dc446 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -50,6 +50,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...);
#include "hu_stuff.h"
#include "i_sound.h"
#include "i_system.h"
+#include "i_threads.h"
#include "i_video.h"
#include "m_argv.h"
#include "m_menu.h"
@@ -68,7 +69,6 @@ int snprintf(char *str, size_t n, const char *fmt, ...);
#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
@@ -102,6 +102,10 @@ int snprintf(char *str, size_t n, const char *fmt, ...);
#include "lua_script.h"
#endif
+#ifdef HAVE_DISCORDRPC
+#include "discord.h"
+#endif
+
// platform independant focus loss
UINT8 window_notinfocus = false;
@@ -196,6 +200,8 @@ void D_ProcessEvents(void)
{
event_t *ev;
+ boolean eaten;
+
for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
{
ev = &events[eventtail];
@@ -217,7 +223,17 @@ void D_ProcessEvents(void)
}
// Menu input
- if (M_Responder(ev))
+#ifdef HAVE_THREADS
+ I_lock_mutex(&m_menu_mutex);
+#endif
+ {
+ eaten = M_Responder(ev);
+ }
+#ifdef HAVE_THREADS
+ I_unlock_mutex(m_menu_mutex);
+#endif
+
+ if (eaten)
continue; // menu ate the event
// Demo input:
@@ -226,7 +242,17 @@ void D_ProcessEvents(void)
continue; // demo ate the event
// console input
- if (CON_Responder(ev))
+#ifdef HAVE_THREADS
+ I_lock_mutex(&con_mutex);
+#endif
+ {
+ eaten = CON_Responder(ev);
+ }
+#ifdef HAVE_THREADS
+ I_unlock_mutex(con_mutex);
+#endif
+
+ if (eaten)
continue; // ate the event
G_Responder(ev);
@@ -522,7 +548,13 @@ static void D_Display(void)
if (gamestate != GS_TIMEATTACK)
CON_Drawer();
+#ifdef HAVE_THREADS
+ I_lock_mutex(&m_menu_mutex);
+#endif
M_Drawer(); // menu is drawn even on top of everything
+#ifdef HAVE_THREADS
+ I_unlock_mutex(m_menu_mutex);
+#endif
// focus lost moved to M_Drawer
//
@@ -698,6 +730,10 @@ void D_SRB2Loop(void)
#ifdef HAVE_BLUA
LUA_Step();
#endif
+
+#ifdef HAVE_DISCORDRPC
+ Discord_RunCallbacks();
+#endif
}
}
@@ -812,9 +848,23 @@ static inline void D_CleanFile(char **filearray)
// Identify the SRB2 version, and IWAD file to use.
// ==========================================================================
+static boolean AddIWAD(void)
+{
+ char * path = va(pandf,srb2path,"srb2.srb");
+
+ if (FIL_ReadFileOK(path))
+ {
+ D_AddFile(path, startupwadfiles);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
static void IdentifyVersion(void)
{
- char *srb2wad1, *srb2wad2;
const char *srb2waddir = NULL;
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
@@ -838,43 +888,21 @@ static void IdentifyVersion(void)
#ifdef _arch_dreamcast
srb2waddir = "/cd";
#else
- srb2waddir = ".";
+ srb2waddir = srb2path;
#endif
}
}
-#if defined (macintosh) && !defined (HAVE_SDL)
- // cwd is always "/" when app is dbl-clicked
- if (!stricmp(srb2waddir, "/"))
- srb2waddir = I_GetWadDir();
-#endif
- // Commercial.
- srb2wad1 = malloc(strlen(srb2waddir)+1+8+1);
- srb2wad2 = malloc(strlen(srb2waddir)+1+8+1);
- if (srb2wad1 == NULL && srb2wad2 == NULL)
- I_Error("No more free memory to look in %s", srb2waddir);
- if (srb2wad1 != NULL)
- sprintf(srb2wad1, pandf, srb2waddir, "srb2.srb");
- if (srb2wad2 != NULL)
- sprintf(srb2wad2, pandf, srb2waddir, "srb2.wad");
+ // Load the IWAD
+ if (! AddIWAD())
+ {
+ I_Error("SRB2.SRB not found! Expected in %s\n", srb2waddir);
+ }
// will be overwritten in case of -cdrom or unix/win home
snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir);
configfile[sizeof configfile - 1] = '\0';
- // Load the IWAD
- if (srb2wad2 != NULL && FIL_ReadFileOK(srb2wad2))
- D_AddFile(srb2wad2, startupwadfiles);
- else if (srb2wad1 != NULL && FIL_ReadFileOK(srb2wad1))
- D_AddFile(srb2wad1, startupwadfiles);
- else
- I_Error("SRB2.SRB/SRB2.WAD not found! Expected in %s, ss files: %s or %s\n", srb2waddir, srb2wad1, srb2wad2);
-
- if (srb2wad1)
- free(srb2wad1);
- if (srb2wad2)
- free(srb2wad2);
-
// if you change the ordering of this or add/remove a file, be sure to update the md5
// checking in D_SRB2Main
@@ -1395,17 +1423,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()))
{
@@ -1581,6 +1598,10 @@ void D_SRB2Main(void)
if (!P_SetupLevel(false))
I_Quit(); // fail so reset game stuff
}
+
+#ifdef HAVE_DISCORDRPC
+ DRPC_Init();
+#endif
}
const char *D_Home(void)
diff --git a/src/d_net.h b/src/d_net.h
index 30de29916..607f3e901 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -19,7 +19,9 @@
#define __D_NET__
// Max computers in a game
-#define MAXNETNODES 64
+// 127 is probably as high as this can go, because
+// SINT8 is used for nodes sometimes >:(
+#define MAXNETNODES 127
#define BROADCASTADDR MAXNETNODES
#define NETSPLITSCREEN // Kart's splitscreen netgame feature
@@ -41,6 +43,8 @@ extern SINT8 nodetoplayer4[MAXNETNODES]; // Say the numplayer for this node if a
extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen
extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game
+extern boolean serverrunning;
+
INT32 Net_GetFreeAcks(boolean urgent);
void Net_AckTicker(void);
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 7d94d2ed2..4a90de333 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -55,6 +55,10 @@
#define CV_RESTRICT 0
#endif
+#ifdef HAVE_DISCORDRPC
+#include "discord.h"
+#endif
+
// ------
// protos
// ------
@@ -77,6 +81,7 @@ static void Got_RandomSeed(UINT8 **cp, INT32 playernum);
static void Got_RunSOCcmd(UINT8 **cp, INT32 playernum);
static void Got_Teamchange(UINT8 **cp, INT32 playernum);
static void Got_Clearscores(UINT8 **cp, INT32 playernum);
+static void Got_DiscordInfo(UINT8 **cp, INT32 playernum);
static void PointLimit_OnChange(void);
static void TimeLimit_OnChange(void);
@@ -704,10 +709,13 @@ void D_RegisterServerCommands(void)
CV_RegisterVar(&cv_showping);
#ifdef SEENAMES
- CV_RegisterVar(&cv_allowseenames);
+ CV_RegisterVar(&cv_allowseenames);
#endif
CV_RegisterVar(&cv_dummyconsvar);
+
+ CV_RegisterVar(&cv_discordinvites);
+ RegisterNetXCmd(XD_DISCORD, Got_DiscordInfo);
}
// =========================================================================
@@ -1000,6 +1008,12 @@ void D_RegisterClientCommands(void)
#if defined(HAVE_BLUA) && defined(LUA_ALLOW_BYTECODE)
COM_AddCommand("dumplua", Command_Dumplua_f);
#endif
+
+#ifdef HAVE_DISCORDRPC
+ CV_RegisterVar(&cv_discordrp);
+ CV_RegisterVar(&cv_discordstreamer);
+ CV_RegisterVar(&cv_discordasks);
+#endif
}
/** Checks if a name (as received from another player) is okay.
@@ -1204,6 +1218,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;
}
@@ -1385,7 +1401,12 @@ static void SendNameAndColor(void)
snacpending++;
// Don't change name if muted
- if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
+ if (player_name_changes[consoleplayer] >= MAXNAMECHANGES)
+ {
+ CV_StealthSet(&cv_playername, player_names[consoleplayer]);
+ HU_AddChatText("\x85*You must wait to change your name again", false);
+ }
+ else if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
CV_StealthSet(&cv_playername, player_names[consoleplayer]);
else // Cleanup name if changing it
CleanupPlayerName(consoleplayer, cv_playername.zstring);
@@ -1509,7 +1530,12 @@ static void SendNameAndColor2(void)
snac2pending++;
// Don't change name if muted
- if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[1])))
+ if (player_name_changes[displayplayers[1]] >= MAXNAMECHANGES)
+ {
+ CV_StealthSet(&cv_playername2, player_names[displayplayers[1]]);
+ HU_AddChatText("\x85*You must wait to change your name again", false);
+ }
+ else if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[1])))
CV_StealthSet(&cv_playername2, player_names[displayplayers[1]]);
else // Cleanup name if changing it
CleanupPlayerName(displayplayers[1], cv_playername2.zstring);
@@ -1624,7 +1650,12 @@ static void SendNameAndColor3(void)
snac3pending++;
// Don't change name if muted
- if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[2])))
+ if (player_name_changes[displayplayers[2]] >= MAXNAMECHANGES)
+ {
+ CV_StealthSet(&cv_playername3, player_names[displayplayers[2]]);
+ HU_AddChatText("\x85*You must wait to change your name again", false);
+ }
+ else if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[2])))
CV_StealthSet(&cv_playername3, player_names[displayplayers[2]]);
else // Cleanup name if changing it
CleanupPlayerName(displayplayers[2], cv_playername3.zstring);
@@ -1747,7 +1778,12 @@ static void SendNameAndColor4(void)
snac4pending++;
// Don't change name if muted
- if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[3])))
+ if (player_name_changes[displayplayers[3]] >= MAXNAMECHANGES)
+ {
+ CV_StealthSet(&cv_playername4, player_names[displayplayers[3]]);
+ HU_AddChatText("\x85*You must wait to change your name again", false);
+ }
+ else if (cv_mute.value && !(server || IsPlayerAdmin(displayplayers[3])))
CV_StealthSet(&cv_playername4, player_names[displayplayers[3]]);
else // Cleanup name if changing it
CleanupPlayerName(displayplayers[3], cv_playername4.zstring);
@@ -1802,8 +1838,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
skin = READUINT8(*cp);
// set name
- if (strcasecmp(player_names[playernum], name) != 0)
- SetPlayerName(playernum, name);
+ if (player_name_changes[playernum] < MAXNAMECHANGES)
+ {
+ if (strcasecmp(player_names[playernum], name) != 0)
+ SetPlayerName(playernum, name);
+ }
// set color
p->skincolor = color % MAXSKINCOLORS;
@@ -1859,6 +1898,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
}
else
SetPlayerSkinByNum(playernum, skin);
+
+#ifdef HAVE_DISCORDRPC
+ if (playernum == consoleplayer)
+ DRPC_UpdatePresence();
+#endif
}
void SendWeaponPref(void)
@@ -2670,6 +2714,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)
@@ -4088,7 +4136,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))
{
@@ -4260,7 +4308,7 @@ static void Command_Delfile(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;
@@ -4355,7 +4403,7 @@ static void Got_Delfilecmd(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);
@@ -4643,6 +4691,10 @@ static void TimeLimit_OnChange(void)
}
else if (netgame || multiplayer)
CONS_Printf(M_GetText("Time limit disabled\n"));
+
+#ifdef HAVE_DISCORDRPC
+ DRPC_UpdatePresence();
+#endif
}
/** Adjusts certain settings to match a changed gametype.
@@ -5668,3 +5720,32 @@ static void KartEliminateLast_OnChange(void)
if (G_RaceGametype() && cv_karteliminatelast.value)
P_CheckRacers();
}
+
+void Got_DiscordInfo(UINT8 **p, INT32 playernum)
+{
+ if (playernum != serverplayer /*&& !IsPlayerAdmin(playernum)*/)
+ {
+ // protect against hacked/buggy client
+ CONS_Alert(CONS_WARNING, M_GetText("Illegal Discord info command received from %s\n"), player_names[playernum]);
+ if (server)
+ {
+ XBOXSTATIC UINT8 buf[2];
+
+ buf[0] = (UINT8)playernum;
+ buf[1] = KICK_MSG_CON_FAIL;
+ SendNetXCmd(XD_KICK, &buf, 2);
+ }
+ return;
+ }
+
+ // Don't do anything with the information if we don't have Discord RP support
+#ifdef HAVE_DISCORDRPC
+ discordInfo.maxPlayers = READUINT8(*p);
+ discordInfo.joinsAllowed = (boolean)READUINT8(*p);
+ discordInfo.everyoneCanInvite = (boolean)READUINT8(*p);
+
+ DRPC_UpdatePresence();
+#else
+ (*p) += 3;
+#endif
+}
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 2d8e5705a..1e1588083 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -178,9 +178,10 @@ typedef enum
XD_MODIFYVOTE, // 23
XD_PICKVOTE, // 24
XD_REMOVEPLAYER,// 25
+ XD_DISCORD, // 26
#ifdef HAVE_BLUA
- XD_LUACMD, // 26
- XD_LUAVAR, // 27
+ XD_LUACMD, // 27
+ XD_LUAVAR, // 28
#endif
MAXNETXCMD
} netxcmd_t;
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 3dc9da68c..c9bfb4ea7 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -108,6 +108,10 @@ char downloaddir[512] = "DOWNLOAD";
#ifdef CLIENT_LOADINGSCREEN
// for cl loading screen
INT32 lastfilenum = -1;
+INT32 downloadcompletednum = 0;
+UINT32 downloadcompletedsize = 0;
+INT32 totalfilesrequestednum = 0;
+UINT32 totalfilesrequestedsize = 0;
#endif
#ifdef HAVE_CURL
@@ -141,7 +145,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)
@@ -207,7 +211,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
filestatus = READUINT8(p); // The first byte is the file status
fileneeded[i].willsend = (UINT8)(filestatus >> 4);
fileneeded[i].totalsize = READUINT32(p); // The four next bytes are the file size
@@ -370,15 +374,17 @@ boolean Got_RequestFilePak(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;
@@ -395,7 +401,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)
{
@@ -424,10 +430,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) &&
@@ -435,36 +450,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
@@ -473,6 +487,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);
@@ -498,6 +513,7 @@ void CL_LoadServerFiles(void)
fileneeded[i].status, s);
}
}
+ return true;
}
// Number of files to send
@@ -858,6 +874,10 @@ void Got_Filetxpak(void)
file->status = FS_FOUND;
CONS_Printf(M_GetText("Downloading %s...(done)\n"),
filename);
+#ifndef NONET
+ downloadcompletednum++;
+ downloadcompletedsize += file->totalsize;
+#endif
}
}
else
@@ -1088,7 +1108,7 @@ void CURLPrepareFile(const char* url, int dfilenum)
// Only allow HTTP and HTTPS
curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
- curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("SRB2Kart/v%d.%d.%d", VERSION/100, VERSION%100, SUBVERSION)); // Set user agent as some servers won't accept invalid user agents.
+ curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("SRB2Kart/v%d.%d", VERSION, SUBVERSION)); // Set user agent as some servers won't accept invalid user agents.
// Follow a redirect request, if sent by the server.
curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1L);
@@ -1169,6 +1189,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 f37df371f..905364e30 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -25,6 +25,7 @@ typedef enum
typedef enum
{
+ FS_NOTCHECKED,
FS_NOTFOUND,
FS_FOUND,
FS_REQUESTED,
@@ -52,6 +53,10 @@ extern char downloaddir[512];
#ifdef CLIENT_LOADINGSCREEN
extern INT32 lastfilenum;
+extern INT32 downloadcompletednum;
+extern UINT32 downloadcompletedsize;
+extern INT32 totalfilesrequestednum;
+extern UINT32 totalfilesrequestedsize;
#endif
#ifdef HAVE_CURL
@@ -65,7 +70,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 SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod,
UINT8 fileid);
diff --git a/src/dehacked.c b/src/dehacked.c
index 96d82fff4..00f4fa96d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -925,6 +925,18 @@ static void readlevelheader(MYFILE *f, INT32 num)
sizeof(mapheaderinfo[num-1]->subttl), va("Level header %d: subtitle", num));
continue;
}
+ else if (fastcmp(word, "LEVELNAME"))
+ {
+ deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
+ sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
+ continue;
+ }
+ else if (fastcmp(word, "ZONETITLE"))
+ {
+ deh_strlcpy(mapheaderinfo[num-1]->zonttl, word2,
+ sizeof(mapheaderinfo[num-1]->zonttl), va("Level header %d: zonetitle", num));
+ continue;
+ }
// Lua custom options also go above, contents may be case sensitive.
if (fastncmp(word, "LUA.", 4))
@@ -987,16 +999,6 @@ static void readlevelheader(MYFILE *f, INT32 num)
}
// Strings that can be truncated
- else if (fastcmp(word, "LEVELNAME"))
- {
- deh_strlcpy(mapheaderinfo[num-1]->lvlttl, word2,
- sizeof(mapheaderinfo[num-1]->lvlttl), va("Level header %d: levelname", num));
- }
- else if (fastcmp(word, "ZONETITLE"))
- {
- deh_strlcpy(mapheaderinfo[num-1]->zonttl, word2,
- sizeof(mapheaderinfo[num-1]->zonttl), va("Level header %d: zonetitle", num));
- }
else if (fastcmp(word, "SCRIPTNAME"))
{
deh_strlcpy(mapheaderinfo[num-1]->scriptname, word2,
@@ -7764,7 +7766,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",
@@ -7775,6 +7776,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",
@@ -8622,13 +8624,11 @@ struct {
{"FF_COLORMAPONLY",FF_COLORMAPONLY}, ///< Only copy the colormap, not the lightlevel
{"FF_GOOWATER",FF_GOOWATER}, ///< Used with ::FF_SWIMMABLE. Makes thick bouncey goop.
-#ifdef ESLOPE
// Slope flags
{"SL_NOPHYSICS",SL_NOPHYSICS}, // Don't do momentum adjustment with this slope
{"SL_NODYNAMIC",SL_NODYNAMIC}, // Slope will never need to move during the level, so don't fuss with recalculating it
{"SL_ANCHORVERTEX",SL_ANCHORVERTEX},// Slope is using a Slope Vertex Thing to anchor its position
{"SL_VERTEXSLOPE",SL_VERTEXSLOPE}, // Slope is built from three Slope Vertex Things
-#endif
// Angles
{"ANG1",ANG1},
@@ -9764,6 +9764,9 @@ static inline int lib_getenum(lua_State *L)
} 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;
}
diff --git a/src/discord.c b/src/discord.c
new file mode 100644
index 000000000..99cc0a3bd
--- /dev/null
+++ b/src/discord.c
@@ -0,0 +1,707 @@
+// 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 (!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 = (G_BattleGametype() ? "miscredplanet" : "miscblueplanet");
+ discordPresence.largeImageText = "Voting";
+ }
+ else
+ {
+ discordPresence.largeImageKey = "misctitle";
+ discordPresence.largeImageText = "Title Screen";
+ }
+
+ // Character info
+ if (Playing() && playeringame[consoleplayer] && !players[consoleplayer].spectator)
+ {
+ // Supported skin names
+ static const char *supportedSkins[] = {
+ // base game
+ "sonic",
+ "tails",
+ "knuckles",
+ "eggman",
+ "metalsonic",
+ // bonus chars
+ "flicky",
+ "motobug",
+ "amy",
+ "mighty",
+ "ray",
+ "espio",
+ "vector",
+ "chao",
+ "gamma",
+ "chaos",
+ "shadow",
+ "rouge",
+ "herochao",
+ "darkchao",
+ "cream",
+ "omega",
+ "blaze",
+ "silver",
+ "wonderboy",
+ "arle",
+ "nights",
+ "sakura",
+ "ulala",
+ "beat",
+ "vyse",
+ "aiai",
+ "kiryu",
+ "aigis",
+ "miku",
+ "doom",
+ NULL
+ };
+
+ boolean customChar = true;
+ UINT8 checkSkin = 0;
+
+ // Character image
+ while (supportedSkins[checkSkin] != NULL)
+ {
+ if (!strcmp(skins[players[consoleplayer].skin].name, supportedSkins[checkSkin]))
+ {
+ snprintf(charimg, 21, "char%s", supportedSkins[checkSkin]);
+ discordPresence.smallImageKey = charimg;
+ customChar = false;
+ break;
+ }
+
+ checkSkin++;
+ }
+
+ if (customChar == true)
+ {
+ // Use the custom character icon!
+ discordPresence.smallImageKey = "charcustom";
+ }
+
+ snprintf(charname, 28, "Character: %s", skins[players[consoleplayer].skin].realname);
+ discordPresence.smallImageText = charname; // Character name
+ }
+
+ if (joinSecretSet == false)
+ {
+ // Not able to join? Flush the request list, if it exists.
+ DRPC_EmptyRequests();
+ }
+
+ Discord_UpdatePresence(&discordPresence);
+}
+
+#endif // HAVE_DISCORDRPC
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/doomdef.h b/src/doomdef.h
index 392ba7605..79ab2e13a 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -30,7 +30,6 @@
#ifdef HAVE_MIXER
//#if !defined(DC) && !defined(_WIN32_WCE) && !defined(_XBOX) && !defined(GP2X)
#define SOUND SOUND_MIXER
- #define NOHS // No HW3SOUND
#ifdef HW3SOUND
#undef HW3SOUND
#endif
@@ -47,7 +46,6 @@
// Use FMOD?
#ifdef HAVE_FMOD
#define SOUND SOUND_FMOD
- #define NOHS // No HW3SOUND
#ifdef HW3SOUND
#undef HW3SOUND
#endif
@@ -64,10 +62,6 @@
#if !defined (HWRENDER) && !defined (NOHW)
#define HWRENDER
#endif
-// judgecutor: 3D sound support
-#if !defined(HW3SOUND) && !defined (NOHS)
-#define HW3SOUND
-#endif
#endif
#if defined (_WIN32) || defined (_WIN32_WCE)
@@ -154,9 +148,9 @@ extern char logfilename[1024];
// we use comprevision and compbranch instead.
#else
#define VERSION 1 // Game version
-#define SUBVERSION 2 // more precise version number
-#define VERSIONSTRING "v1.2"
-#define VERSIONSTRINGW L"v1.2"
+#define SUBVERSION 3 // more precise version number
+#define VERSIONSTRING "v1.3"
+#define VERSIONSTRINGW L"v1.3"
// Hey! If you change this, add 1 to the MODVERSION below! Otherwise we can't force updates!
// And change CMakeLists.txt, for CMake users!
// AND appveyor.yml, for the build bots!
@@ -187,32 +181,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.
@@ -229,7 +204,7 @@ extern char logfilename[1024];
// it's only for detection of the version the player is using so the MS can alert them of an update.
// Only set it higher, not lower, obviously.
// Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 6
+#define MODVERSION 7
// Filter consvars by version
// To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically.
@@ -606,11 +581,9 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
/// Kalaron/Eternity Engine slope code (SRB2CB ported)
#define ESLOPE
-#ifdef ESLOPE
/// Backwards compatibility with SRB2CB's slope linedef types.
/// \note A simple shim that prints a warning.
#define ESLOPE_TYPESHIM
-#endif
/// Delete file while the game is running.
/// \note EXTREMELY buggy, tends to crash game.
@@ -681,4 +654,10 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
/// Hardware renderer: OpenGL
#define GL_SHADERS
+#if defined (HAVE_CURL) && ! defined (NONET)
+#define MASTERSERVER
+#else
+#undef UPDATE_ALERT
+#endif
+
#endif // __DOOMDEF__
diff --git a/src/f_finale.c b/src/f_finale.c
index 909ca2611..ade26bbe0 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -24,6 +24,7 @@
#include "w_wad.h"
#include "z_zone.h"
#include "i_system.h"
+#include "i_threads.h"
#include "m_menu.h"
#include "dehacked.h"
#include "g_input.h"
@@ -317,7 +318,13 @@ void F_IntroDrawer(void)
{
I_OsPolling();
I_UpdateNoBlit();
+#ifdef HAVE_THREADS
+ I_lock_mutex(&m_menu_mutex);
+#endif
M_Drawer(); // menu is drawn even on top of wipes
+#ifdef HAVE_THREADS
+ I_unlock_mutex(m_menu_mutex);
+#endif
I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
}
}
diff --git a/src/f_wipe.c b/src/f_wipe.c
index bb1397bcf..8bcb12782 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -22,6 +22,7 @@
#include "z_zone.h"
#include "i_system.h"
+#include "i_threads.h"
#include "m_menu.h"
#include "console.h"
#include "d_main.h"
@@ -379,7 +380,15 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
I_UpdateNoBlit();
if (drawMenu)
+ {
+#ifdef HAVE_THREADS
+ I_lock_mutex(&m_menu_mutex);
+#endif
M_Drawer(); // menu is drawn even on top of wipes
+#ifdef HAVE_THREADS
+ I_unlock_mutex(m_menu_mutex);
+#endif
+ }
I_FinishUpdate(); // page flip or blit buffer
diff --git a/src/g_game.c b/src/g_game.c
index dd0d8460b..065ce7a33 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -51,6 +51,10 @@
#include "md5.h" // demo checksums
#include "k_kart.h" // SRB2kart
+#ifdef HAVE_DISCORDRPC
+#include "discord.h"
+#endif
+
gameaction_t gameaction;
gamestate_t gamestate = GS_NULL;
UINT8 ultimatemode = false;
@@ -544,6 +548,8 @@ char player_names[MAXPLAYERS][MAXPLAYERNAME+1] =
"Player 16"
}; // SRB2kart - removed Players 17 through 32
+INT32 player_name_changes[MAXPLAYERS];
+
INT16 rw_maximums[NUM_WEAPONS] =
{
800, // MAX_INFINITY
@@ -2498,6 +2504,11 @@ void G_Ticker(boolean run)
spectatedelay3--;
if (spectatedelay4)
spectatedelay4--;
+
+ if (gametic % NAMECHANGERATE == 0)
+ {
+ memset(player_name_changes, 0, sizeof player_name_changes);
+ }
}
}
@@ -3647,7 +3658,7 @@ INT16 G_RandMap(INT16 tolflags, INT16 pprevmap, boolean ignorebuffer, UINT8 maph
void G_AddMapToBuffer(INT16 map)
{
- INT16 bufx, refreshnum = (TOLMaps(G_TOLFlag(gametype)) / 2) + 1;
+ INT16 bufx, refreshnum = max(0, TOLMaps(G_TOLFlag(gametype))-3);
// Add the map to the buffer.
for (bufx = NUMMAPS-1; bufx > 0; bufx--)
@@ -4717,7 +4728,7 @@ char *G_BuildMapTitle(INT32 mapnum)
}
else if (!(mapheaderinfo[mapnum-1]->levelflags & LF_NOZONE))
{
- zonetext = M_GetText("ZONE");
+ zonetext = M_GetText("Zone");
len += strlen(zonetext) + 1; // ' ' + zonetext
}
if (strlen(mapheaderinfo[mapnum-1]->actnum) > 0)
@@ -6326,8 +6337,8 @@ void G_RecordDemo(const char *name)
maxsize = 1024*1024*2;
if (M_CheckParm("-maxdemo") && M_IsNextParm())
maxsize = atoi(M_GetNextParm()) * 1024;
-// if (demobuffer)
-// free(demobuffer);
+ if (demobuffer)
+ free(demobuffer);
demo_p = NULL;
demobuffer = malloc(maxsize);
demoend = demobuffer + maxsize;
@@ -6368,7 +6379,9 @@ void G_BeginRecording(void)
demoflags |= DF_ENCORE;
#ifdef HAVE_BLUA
- demoflags |= DF_LUAVARS;
+ if (!modeattacking) // Ghosts don't read luavars, and you shouldn't ever need to save Lua in replays, you doof!
+ // SERIOUSLY THOUGH WHY WOULD YOU LOAD HOSTMOD AND RECORD A GHOST WITH IT !????
+ demoflags |= DF_LUAVARS;
#endif
// Setup header.
@@ -6379,7 +6392,7 @@ void G_BeginRecording(void)
// Full replay title
demo_p += 64;
- snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Record Attack" : connectedservername);
+ snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Time Attack" : connectedservername);
// demo checksum
demo_p += 16;
@@ -6474,8 +6487,9 @@ void G_BeginRecording(void)
WRITEUINT8(demo_p, 0xFF); // Denote the end of the player listing
#ifdef HAVE_BLUA
- // player lua vars, always saved even if empty
- LUA_ArchiveDemo();
+ // player lua vars, always saved even if empty... Unless it's record attack.
+ if (!modeattacking)
+ LUA_ArchiveDemo();
#endif
memset(&oldcmd,0,sizeof(oldcmd));
@@ -7153,6 +7167,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7181,6 +7196,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7193,6 +7209,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7212,6 +7229,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7272,6 +7290,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7342,6 +7361,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7355,6 +7375,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7379,6 +7400,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7422,6 +7444,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7477,6 +7500,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7491,6 +7515,7 @@ void G_DoPlayDemo(char *defdemoname)
M_StartMessage(msg, NULL, MM_NOTHING);
Z_Free(pdemoname);
Z_Free(demobuffer);
+ demobuffer = NULL;
demo.playback = false;
demo.title = false;
return;
@@ -7543,6 +7568,7 @@ void G_DoPlayDemo(char *defdemoname)
if (!gL) // No Lua state! ...I guess we'll just start one...
LUA_ClearState();
+ // No modeattacking check, DF_LUAVARS won't be present here.
LUA_UnArchiveDemo();
}
#endif
@@ -8140,6 +8166,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(void)
saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file.
}
free(demobuffer);
+ demobuffer = NULL;
metalrecording = false;
if (saved)
I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap));
@@ -8320,6 +8347,7 @@ void G_SaveDemo(void)
if (FIL_WriteFile(va(pandf, srb2home, demoname), demobuffer, demo_p - demobuffer)) // finally output the file.
demo.savemode = DSM_SAVED;
free(demobuffer);
+ demobuffer = NULL;
demo.recording = false;
if (modeattacking != ATTACKING_RECORD)
@@ -8389,6 +8417,9 @@ boolean G_DemoTitleResponder(event_t *ev)
void G_SetGamestate(gamestate_t newstate)
{
gamestate = newstate;
+#ifdef HAVE_DISCORDRPC
+ DRPC_UpdatePresence();
+#endif
}
/* These functions handle the exitgame flag. Before, when the user
diff --git a/src/g_game.h b/src/g_game.h
index 0edc13c74..493be5d49 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -26,7 +26,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/hardware/hw_main.c b/src/hardware/hw_main.c
index c7ff5a4af..9b7b31e49 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -4733,6 +4733,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
}
R_SetupFrame(player, false); // This can stay false because it is only used to set viewsky in r_main.c, which isn't used here
+ framecount++; // for timedemo
HWR_RenderFrame(viewnumber, player, false);
}
diff --git a/src/http-mserv.c b/src/http-mserv.c
new file mode 100644
index 000000000..c149eb9b2
--- /dev/null
+++ b/src/http-mserv.c
@@ -0,0 +1,540 @@
+// 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 HTTP based master server
+
+/*
+Documentation available here.
+
+
+*/
+
+#ifdef HAVE_CURL
+#include
+#endif
+
+#include "doomdef.h"
+#include "d_clisrv.h"
+#include "command.h"
+#include "m_argv.h"
+#include "m_menu.h"
+#include "mserv.h"
+#include "i_tcp.h"/* for current_port */
+#include "i_threads.h"
+
+/* reasonable default I guess?? */
+#define DEFAULT_BUFFER_SIZE (4096)
+
+/* I just stop myself from making macros anymore. */
+#define Blame( ... ) \
+ CONS_Printf("\x85" __VA_ARGS__)
+
+static void MasterServer_Debug_OnChange (void);
+
+consvar_t cv_masterserver_timeout = {
+ "masterserver_timeout", "5", CV_SAVE, CV_Unsigned,
+ NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
+};
+
+consvar_t cv_masterserver_debug = {
+ "masterserver_debug", "Off", CV_SAVE|CV_CALL, CV_OnOff,
+ MasterServer_Debug_OnChange, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
+};
+
+consvar_t cv_masterserver_token = {
+ "masterserver_token", "", CV_SAVE, NULL,
+ NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
+};
+
+#ifdef MASTERSERVER
+
+static int hms_started;
+
+static char *hms_api;
+#ifdef HAVE_THREADS
+static I_mutex hms_api_mutex;
+#endif
+
+static char *hms_server_token;
+
+struct HMS_buffer
+{
+ CURL *curl;
+ char *buffer;
+ int needle;
+ int end;
+};
+
+static void
+Contact_error (void)
+{
+ CONS_Alert(CONS_ERROR,
+ "There was a problem contacting the master server...\n"
+ );
+}
+
+static size_t
+HMS_on_read (char *s, size_t _1, size_t n, void *userdata)
+{
+ struct HMS_buffer *buffer;
+ size_t blocks;
+
+ (void)_1;
+
+ buffer = userdata;
+
+ if (n >= (size_t)( buffer->end - buffer->needle ))
+ {
+ /* resize to next multiple of buffer size */
+ blocks = ( n / DEFAULT_BUFFER_SIZE + 1 );
+ buffer->end += ( blocks * DEFAULT_BUFFER_SIZE );
+
+ buffer->buffer = realloc(buffer->buffer, buffer->end);
+ }
+
+ memcpy(&buffer->buffer[buffer->needle], s, n);
+ buffer->needle += n;
+
+ return n;
+}
+
+static struct HMS_buffer *
+HMS_connect (const char *format, ...)
+{
+ va_list ap;
+ CURL *curl;
+ char *url;
+ char *quack_token;
+ size_t seek;
+ size_t token_length;
+ struct HMS_buffer *buffer;
+
+ if (! hms_started)
+ {
+ if (curl_global_init(CURL_GLOBAL_ALL) != 0)
+ {
+ Contact_error();
+ Blame("From curl_global_init.\n");
+ return NULL;
+ }
+ else
+ {
+ atexit(curl_global_cleanup);
+ hms_started = 1;
+ }
+ }
+
+ curl = curl_easy_init();
+
+ if (! curl)
+ {
+ Contact_error();
+ Blame("From curl_easy_init.\n");
+ return NULL;
+ }
+
+ 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);
+ }
+ else
+ {
+ quack_token = NULL;
+ token_length = 0;
+ }
+
+#ifdef HAVE_THREADS
+ I_lock_mutex(&hms_api_mutex);
+#endif
+
+ seek = strlen(hms_api) + 1;/* + '/' */
+
+ va_start (ap, format);
+ url = malloc(seek + vsnprintf(0, 0, format, ap) +
+ sizeof "?v=2" - 1 +
+ token_length + 1);
+ va_end (ap);
+
+ sprintf(url, "%s/", hms_api);
+
+#ifdef HAVE_THREADS
+ I_unlock_mutex(hms_api_mutex);
+#endif
+
+ va_start (ap, 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);
+
+ CONS_Printf("HMS: connecting '%s'...\n", url);
+
+ buffer = malloc(sizeof *buffer);
+ buffer->curl = curl;
+ buffer->end = DEFAULT_BUFFER_SIZE;
+ buffer->buffer = malloc(buffer->end);
+ buffer->needle = 0;
+
+ if (cv_masterserver_debug.value)
+ {
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ curl_easy_setopt(curl, CURLOPT_STDERR, logstream);
+ }
+
+ if (M_CheckParm("-bindaddr") && M_IsNextParm())
+ {
+ curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm());
+ }
+
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
+
+ curl_free(quack_token);
+ free(url);
+
+ return buffer;
+}
+
+static int
+HMS_do (struct HMS_buffer *buffer)
+{
+ CURLcode cc;
+ long status;
+
+ char *p;
+
+ cc = curl_easy_perform(buffer->curl);
+
+ if (cc != CURLE_OK)
+ {
+ Contact_error();
+ Blame(
+ "From curl_easy_perform: %s\n",
+ curl_easy_strerror(cc)
+ );
+ return 0;
+ }
+
+ buffer->buffer[buffer->needle] = '\0';
+
+ curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status);
+
+ if (status != 200)
+ {
+ p = strchr(buffer->buffer, '\n');
+
+ if (p)
+ *p = '\0';
+
+ Contact_error();
+ Blame(
+ "Master server error %ld: %s%s\n",
+ status,
+ buffer->buffer,
+ ( (p) ? "" : " (malformed)" )
+ );
+
+ return 0;
+ }
+ else
+ return 1;
+}
+
+static void
+HMS_end (struct HMS_buffer *buffer)
+{
+ curl_easy_cleanup(buffer->curl);
+ free(buffer->buffer);
+ free(buffer);
+}
+
+int
+HMS_register (void)
+{
+ struct HMS_buffer *hms;
+ int ok;
+
+ char post[256];
+
+ char *contact;
+
+ hms = HMS_connect(
+ "games/%s/%d/servers/register", SRB2APPLICATION, MODVERSION);
+
+ if (! hms)
+ return 0;
+
+ contact = curl_easy_escape(hms->curl, cv_server_contact.string, 0);
+
+ snprintf(post, sizeof post,
+ "port=%d&"
+ "contact=%s",
+
+ current_port,
+
+ contact
+ );
+
+ curl_free(contact);
+
+ curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
+
+ ok = HMS_do(hms);
+
+ if (ok)
+ {
+ hms_server_token = strdup(strtok(hms->buffer, "\n"));
+ }
+
+ HMS_end(hms);
+
+ return ok;
+}
+
+int
+HMS_unlist (void)
+{
+ struct HMS_buffer *hms;
+ int ok;
+
+ hms = HMS_connect("servers/%s/unlist", hms_server_token);
+
+ if (! hms)
+ return 0;
+
+ curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST");
+
+ ok = HMS_do(hms);
+ HMS_end(hms);
+
+ free(hms_server_token);
+
+ return ok;
+}
+
+int
+HMS_update (void)
+{
+ struct HMS_buffer *hms;
+ int ok;
+
+ char post[256];
+
+ char *title;
+
+ hms = HMS_connect("servers/%s/update", hms_server_token);
+
+ if (! hms)
+ return 0;
+
+ title = curl_easy_escape(hms->curl, cv_servername.string, 0);
+
+ snprintf(post, sizeof post,
+ "title=%s",
+ title
+ );
+
+ curl_free(title);
+
+ curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
+
+ ok = HMS_do(hms);
+ HMS_end(hms);
+
+ return ok;
+}
+
+void
+HMS_list_servers (void)
+{
+ struct HMS_buffer *hms;
+
+ hms = HMS_connect("games/%s/%d/servers", SRB2APPLICATION, MODVERSION);
+
+ if (! hms)
+ return;
+
+ if (HMS_do(hms))
+ {
+ CONS_Printf("%s\n", hms->buffer);
+ }
+
+ HMS_end(hms);
+}
+
+msg_server_t *
+HMS_fetch_servers (msg_server_t *list, int query_id)
+{
+ struct HMS_buffer *hms;
+
+ int doing_shit;
+
+ char *address;
+ char *port;
+ char *contact;
+
+ char *end;
+ char *p;
+
+ int i;
+
+ (void)query_id;
+
+ hms = HMS_connect("games/%s/%d/servers", SRB2APPLICATION, MODVERSION);
+
+ if (! hms)
+ return NULL;
+
+ if (HMS_do(hms))
+ {
+ doing_shit = 1;
+
+ p = hms->buffer;
+ i = 0;
+
+ while (i < MAXSERVERLIST && ( end = strchr(p, '\n') ))
+ {
+ *end = '\0';
+
+ address = strtok(p, " ");
+ port = strtok(0, " ");
+ contact = strtok(0, "");
+
+ if (address && port)
+ {
+#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
+
+ strlcpy(list[i].ip, address, sizeof list[i].ip);
+ strlcpy(list[i].port, port, sizeof list[i].port);
+
+ if (contact)
+ {
+ strlcpy(list[i].contact, contact, sizeof list[i].contact);
+ }
+
+ list[i].header.buffer[0] = 1;
+
+ i++;
+
+ p = ( end + 1 );/* skip server delimiter */
+ }
+ else
+ {
+ /* malformed so quit the parsing */
+ break;
+ }
+ }
+
+ if (doing_shit)
+ list[i].header.buffer[0] = 0;
+ }
+ else
+ list = NULL;
+
+ HMS_end(hms);
+
+ return list;
+}
+
+int
+HMS_compare_mod_version (char *buffer, size_t buffer_size)
+{
+ struct HMS_buffer *hms;
+ int ok;
+
+ char *version;
+ char *version_name;
+
+ hms = HMS_connect("games/%s/version", SRB2APPLICATION);
+
+ if (! hms)
+ return 0;
+
+ ok = 0;
+
+ if (HMS_do(hms))
+ {
+ version = strtok(hms->buffer, " ");
+ version_name = strtok(0, "\n");
+
+ if (version && version_name)
+ {
+ if (atoi(version) != MODVERSION)
+ {
+ strlcpy(buffer, version_name, buffer_size);
+ ok = 1;
+ }
+ else
+ ok = -1;
+ }
+ }
+
+ HMS_end(hms);
+
+ 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)
+{
+#ifdef HAVE_THREADS
+ I_lock_mutex(&hms_api_mutex);
+#endif
+ {
+ free(hms_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/i_tcp.c b/src/i_tcp.c
index f58aa22bc..1f1cf4f22 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -246,7 +246,8 @@ static size_t numbans = 0;
static boolean SOCK_bannednode[MAXNETNODES+1]; /// \note do we really need the +1?
static boolean init_tcp_driver = false;
-static char port_name[8] = DEFAULTPORT;
+static const char *serverport_name = DEFAULTPORT;
+static const char *clientport_name;/* any port */
#ifndef NONET
@@ -924,6 +925,7 @@ static boolean UDP_Socket(void)
#ifdef HAVE_IPV6
const INT32 b_ipv6 = M_CheckParm("-ipv6");
#endif
+ const char *serv;
for (s = 0; s < mysocketses; s++)
@@ -939,11 +941,16 @@ static boolean UDP_Socket(void)
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
+ if (serverrunning)
+ serv = serverport_name;
+ else
+ serv = clientport_name;
+
if (M_CheckParm("-bindaddr"))
{
while (M_IsNextParm())
{
- gaie = I_getaddrinfo(M_GetNextParm(), port_name, &hints, &ai);
+ gaie = I_getaddrinfo(M_GetNextParm(), serv, &hints, &ai);
if (gaie == 0)
{
runp = ai;
@@ -964,7 +971,7 @@ static boolean UDP_Socket(void)
}
else
{
- gaie = I_getaddrinfo("0.0.0.0", port_name, &hints, &ai);
+ gaie = I_getaddrinfo("0.0.0.0", serv, &hints, &ai);
if (gaie == 0)
{
runp = ai;
@@ -979,8 +986,8 @@ static boolean UDP_Socket(void)
#ifdef HAVE_MINIUPNPC
if (UPNP_support)
{
- I_UPnP_rem(port_name, "UDP");
- I_UPnP_add(NULL, port_name, "UDP");
+ I_UPnP_rem(serverport_name, "UDP");
+ I_UPnP_add(NULL, serverport_name, "UDP");
}
#endif
}
@@ -997,7 +1004,7 @@ static boolean UDP_Socket(void)
{
while (M_IsNextParm())
{
- gaie = I_getaddrinfo(M_GetNextParm(), port_name, &hints, &ai);
+ gaie = I_getaddrinfo(M_GetNextParm(), serv, &hints, &ai);
if (gaie == 0)
{
runp = ai;
@@ -1018,7 +1025,7 @@ static boolean UDP_Socket(void)
}
else
{
- gaie = I_getaddrinfo("::", port_name, &hints, &ai);
+ gaie = I_getaddrinfo("::", serv, &hints, &ai);
if (gaie == 0)
{
runp = ai;
@@ -1475,15 +1482,19 @@ boolean I_InitTcpNetwork(void)
if (!I_InitTcpDriver())
return false;
- if (M_CheckParm("-port"))
+ if (M_CheckParm("-port") || M_CheckParm("-serverport"))
// Combined -udpport and -clientport into -port
// As it was really redundant having two seperate parms that does the same thing
+ /* Sorry Steel, I'm adding these back. But -udpport is a stupid name. */
{
- if (M_IsNextParm())
- strcpy(port_name, M_GetNextParm());
- else
- strcpy(port_name, "0");
+ /*
+ If it's NULL, that's okay! Because then
+ we'll get a random port from getaddrinfo.
+ */
+ serverport_name = M_GetNextParm();
}
+ if (M_CheckParm("-clientport"))
+ clientport_name = M_GetNextParm();
// parse network game options,
if (M_CheckParm("-server") || dedicated)
diff --git a/src/i_threads.h b/src/i_threads.h
new file mode 100644
index 000000000..45a3dcc3e
--- /dev/null
+++ b/src/i_threads.h
@@ -0,0 +1,39 @@
+// SONIC ROBO BLAST 2 KART
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file i_threads.h
+/// \brief Multithreading abstraction
+
+#ifdef HAVE_THREADS
+
+#ifndef I_THREADS_H
+#define I_THREADS_H
+
+typedef void (*I_thread_fn)(void *userdata);
+
+typedef void * I_mutex;
+typedef void * I_cond;
+
+void I_start_threads (void);
+void I_stop_threads (void);
+
+void I_spawn_thread (const char *name, I_thread_fn, void *userdata);
+
+/* check in your thread whether to return early */
+int I_thread_is_stopped (void);
+
+void I_lock_mutex (I_mutex *);
+void I_unlock_mutex (I_mutex);
+
+void I_hold_cond (I_cond *, I_mutex);
+
+void I_wake_one_cond (I_cond *);
+void I_wake_all_cond (I_cond *);
+
+#endif/*I_THREADS_H*/
+#endif/*HAVE_THREADS*/
diff --git a/src/k_kart.c b/src/k_kart.c
index 2e8ca1773..e6afebc78 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -666,31 +666,31 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS][10] =
/*Jawz x2*/ { 0, 0, 0, 1, 2, 0, 0, 0, 0, 0 } // Jawz x2
};
-static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][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
- /*Hyudoro*/ { 1, 1, 0, 0, 0, 0 }, // Hyudoro
- /*Pogo Spring*/ { 1, 1, 0, 0, 0, 0 }, // Pogo Spring
- /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
- /*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
+ /*Hyudoro*/ { 2, 0 }, // Hyudoro
+ /*Pogo Spring*/ { 2, 0 }, // Pogo Spring
+ /*Kitchen Sink*/ { 0, 0 }, // Kitchen Sink
+ /*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
};
/** \brief Item Roulette for Kart
@@ -898,6 +898,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp
newodds = 0;
else
POWERITEMODDS(newodds);
+ break;
case KITEM_HYUDORO:
if ((hyubgone > 0) || COOLDOWNONSTART)
newodds = 0;
@@ -922,12 +923,15 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT3
UINT8 distlen = 0;
boolean oddsvalid[10];
+ // Unused now, oops :V
+ (void)bestbumper;
+
for (i = 0; i < 10; i++)
{
INT32 j;
boolean available = false;
- if (G_BattleGametype() && i > 5)
+ if (G_BattleGametype() && i > 1)
{
oddsvalid[i] = false;
break;
@@ -961,24 +965,20 @@ static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pingame, INT3
if (G_BattleGametype()) // Battle Mode
{
- if (oddsvalid[0]) SETUPDISTTABLE(0,1);
- if (oddsvalid[1]) SETUPDISTTABLE(1,1);
- if (oddsvalid[2]) SETUPDISTTABLE(2,1);
- if (oddsvalid[3]) SETUPDISTTABLE(3,1);
- if (oddsvalid[4]) 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;
- useodds = disttable[(wantedpos * distlen) / 5];
+ useodds = 0;
+
+ if (oddsvalid[0] == false && oddsvalid[1] == true)
+ {
+ // try to use karma odds as a fallback
+ useodds = 1;
+ }
}
}
else
@@ -1061,6 +1061,10 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd)
bestbumper = players[i].kartstuff[k_bumper];
}
+ // No forced SPB in 1v1s, it has to be randomly rolled
+ if (pingame <= 2)
+ dontforcespb = true;
+
// This makes the roulette produce the random noises.
if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsDisplayPlayer(player) && !demo.freecam)
{
@@ -1372,30 +1376,72 @@ 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
\return boolean
*/
-static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
+static UINT8 K_CheckOffroadCollide(mobj_t *mo)
{
- UINT8 i;
- sector_t *sec2;
+ // 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));
- sec2 = P_ThingOnSpecial3DFloor(mo);
-
- for (i = 2; i < 5; i++)
+ for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
{
- if ((sec2 && GETSECSPECIAL(sec2->special, 1) == i)
- || (P_IsObjectOnRealGround(mo, sec) && GETSECSPECIAL(sec->special, 1) == i))
- 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
@@ -1407,14 +1453,12 @@ static UINT8 K_CheckOffroadCollide(mobj_t *mo, sector_t *sec)
static void K_UpdateOffroad(player_t *player)
{
fixed_t offroad;
- sector_t *nextsector = R_PointInSubsector(
- player->mo->x + player->mo->momx*2, player->mo->y + player->mo->momy*2)->sector;
- UINT8 offroadstrength = K_CheckOffroadCollide(player->mo, nextsector);
+ UINT8 offroadstrength = K_CheckOffroadCollide(player->mo);
// If you are in offroad, a timer starts.
if (offroadstrength)
{
- if (K_CheckOffroadCollide(player->mo, player->mo->subsector->sector) && player->kartstuff[k_offroad] == 0)
+ if (/*K_CheckOffroadCollide(player->mo) &&*/ player->kartstuff[k_offroad] == 0) // With the way offroad is detected now that first check is no longer necessary. -Lat'
player->kartstuff[k_offroad] = (TICRATE/2);
if (player->kartstuff[k_offroad] > 0)
@@ -2893,14 +2937,12 @@ void K_SpawnBoostTrail(player_t *player)
{
newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale));
newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale));
-#ifdef ESLOPE
if (player->mo->standingslope)
{
ground = P_GetZAt(player->mo->standingslope, newx, newy);
if (player->mo->eflags & MFE_VERTICALFLIP)
ground -= FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale);
}
-#endif
flame = P_SpawnMobj(newx, newy, ground, MT_SNEAKERTRAIL);
P_SetTarget(&flame->target, player->mo);
@@ -3312,7 +3354,8 @@ void K_PuntMine(mobj_t *thismine, mobj_t *punter)
if (!thismine || P_MobjWasRemoved(thismine))
return;
- if (thismine->type == MT_SSMINE_SHIELD) // Create a new mine
+ //This guarantees you hit a mine being dragged
+ if (thismine->type == MT_SSMINE_SHIELD) // Create a new mine, and clean up the old one
{
mine = P_SpawnMobj(thismine->x, thismine->y, thismine->z, MT_SSMINE);
P_SetTarget(&mine->target, thismine->target);
@@ -3320,7 +3363,19 @@ 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;
+ }
+
P_RemoveMobj(thismine);
+
}
else
mine = thismine;
@@ -3604,9 +3659,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound)
if (mo->eflags & MFE_SPRUNG)
return;
-#ifdef ESLOPE
mo->standingslope = NULL;
-#endif
mo->eflags |= MFE_SPRUNG;
@@ -3870,6 +3923,62 @@ void K_DropItems(player_t *player)
K_StripItems(player);
}
+void K_DropRocketSneaker(player_t *player)
+{
+ mobj_t *shoe = player->mo;
+ fixed_t flingangle;
+ boolean leftshoe = true; //left shoe is first
+
+ if (!(player->mo && !P_MobjWasRemoved(player->mo) && player->mo->hnext && !P_MobjWasRemoved(player->mo->hnext)))
+ return;
+
+ while ((shoe = shoe->hnext) && !P_MobjWasRemoved(shoe))
+ {
+ if (shoe->type != MT_ROCKETSNEAKER)
+ return; //woah, not a rocketsneaker, bail! safeguard in case this gets used when you're holding non-rocketsneakers
+
+ shoe->flags2 &= ~MF2_DONTDRAW;
+ shoe->flags &= ~MF_NOGRAVITY;
+ shoe->angle += ANGLE_45;
+
+ if (shoe->eflags & MFE_VERTICALFLIP)
+ shoe->z -= shoe->height;
+ else
+ shoe->z += shoe->height;
+
+ //left shoe goes off tot eh left, right shoe off to the right
+ if (leftshoe)
+ 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);
+ shoe->momx += shoe->target->momx;
+ shoe->momy += shoe->target->momy;
+ shoe->momz += shoe->target->momz;
+ shoe->extravalue2 = 1;
+
+ leftshoe = false;
+ }
+ P_SetTarget(&player->mo->hnext, NULL);
+ player->kartstuff[k_rocketsneakertimer] = 0;
+}
+
+void K_DropKitchenSink(player_t *player)
+{
+ if (!(player->mo && !P_MobjWasRemoved(player->mo) && player->mo->hnext && !P_MobjWasRemoved(player->mo->hnext)))
+ return;
+
+ if (player->mo->hnext->type != MT_SINK_SHIELD)
+ return; //so we can just call this function regardless of what is being held
+
+ P_KillMobj(player->mo->hnext, NULL, NULL);
+
+ P_SetTarget(&player->mo->hnext, NULL);
+}
+
// When an item in the hnext chain dies.
void K_RepairOrbitChain(mobj_t *orbit)
{
@@ -4490,6 +4599,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
fast->momx = 3*player->mo->momx/4;
fast->momy = 3*player->mo->momy/4;
fast->momz = 3*player->mo->momz/4;
+ P_SetTarget(&fast->target, player->mo); // easier lua access
K_MatchGenericExtraFlags(fast, player->mo);
}
@@ -5195,12 +5305,12 @@ void K_KartUpdatePosition(player_t *player)
//
void K_StripItems(player_t *player)
{
+ K_DropRocketSneaker(player);
+ K_DropKitchenSink(player);
player->kartstuff[k_itemtype] = KITEM_NONE;
player->kartstuff[k_itemamount] = 0;
player->kartstuff[k_itemheld] = 0;
- player->kartstuff[k_rocketsneakertimer] = 0;
-
if (!player->kartstuff[k_itemroulette] || player->kartstuff[k_roulettetype] != 2)
{
player->kartstuff[k_itemroulette] = 0;
diff --git a/src/k_kart.h b/src/k_kart.h
index 86ae0fcc5..796065404 100644
--- a/src/k_kart.h
+++ b/src/k_kart.h
@@ -55,6 +55,8 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue);
INT32 K_GetKartDriftSparkValue(player_t *player);
void K_KartUpdatePosition(player_t *player);
void K_DropItems(player_t *player);
+void K_DropRocketSneaker(player_t *player);
+void K_DropKitchenSink(player_t *player);
void K_StripItems(player_t *player);
void K_StripOther(player_t *player);
void K_MomentumToFacing(player_t *player);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index d7292cd60..56b1a5a54 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -14,9 +14,7 @@
#ifdef HAVE_BLUA
#include "p_local.h"
#include "p_setup.h" // So we can have P_SetupLevelSky
-#ifdef ESLOPE
#include "p_slopes.h" // P_GetZAt
-#endif
#include "z_zone.h"
#include "r_main.h"
#include "r_things.h"
@@ -1575,7 +1573,6 @@ static int lib_evCrumbleChain(lua_State *L)
return 0;
}
-#ifdef ESLOPE
// P_SLOPES
////////////
@@ -1591,7 +1588,6 @@ static int lib_pGetZAt(lua_State *L)
lua_pushfixed(L, P_GetZAt(slope, x, y));
return 1;
}
-#endif
// R_DEFS
////////////
@@ -3083,10 +3079,8 @@ static luaL_Reg lib[] = {
{"P_StartQuake",lib_pStartQuake},
{"EV_CrumbleChain",lib_evCrumbleChain},
-#ifdef ESLOPE
// p_slopes
{"P_GetZAt",lib_pGetZAt},
-#endif
// r_defs
{"R_PointToAngle",lib_rPointToAngle},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index dabbdd9f6..0e705e2ea 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -79,9 +79,7 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
{
INT32 offset;
const INT32 *list; // Big blockmap
-#ifdef POLYOBJECTS
polymaplink_t *plink; // haleyjd 02/22/06
-#endif
line_t *ld;
if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
@@ -89,7 +87,6 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
offset = y*bmapwidth + x;
-#ifdef POLYOBJECTS
// haleyjd 02/22/06: consider polyobject lines
plink = polyblocklinks[offset];
@@ -132,7 +129,6 @@ static UINT8 lib_searchBlockmap_Lines(lua_State *L, INT32 x, INT32 y, mobj_t *th
}
plink = (polymaplink_t *)(plink->link.next);
}
-#endif
offset = *(blockmap + offset); // offset = blockmap[y*bmapwidth+x];
diff --git a/src/lua_libs.h b/src/lua_libs.h
index cb1cb49c7..00dd9c858 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -40,11 +40,9 @@ extern lua_State *gL;
#define META_SUBSECTOR "SUBSECTOR_T*"
#define META_SECTOR "SECTOR_T*"
#define META_FFLOOR "FFLOOR_T*"
-#ifdef ESLOPE
#define META_SLOPE "PSLOPE_T*"
#define META_VECTOR2 "VECTOR2_T"
#define META_VECTOR3 "VECTOR3_T"
-#endif
#define META_MAPHEADER "MAPHEADER_T*"
#define META_CVAR "CONSVAR_T*"
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 19292b3d6..41875ea98 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -16,9 +16,7 @@
#include "p_local.h"
#include "p_setup.h"
#include "z_zone.h"
-#ifdef ESLOPE
#include "p_slopes.h"
-#endif
#include "r_main.h"
#include "lua_script.h"
@@ -43,13 +41,9 @@ enum sector_e {
sector_heightsec,
sector_camsec,
sector_lines,
-#ifdef ESLOPE
sector_ffloors,
sector_fslope,
sector_cslope
-#else
- sector_ffloors
-#endif
};
static const char *const sector_opt[] = {
@@ -66,10 +60,8 @@ static const char *const sector_opt[] = {
"camsec",
"lines",
"ffloors",
-#ifdef ESLOPE
"f_slope",
"c_slope",
-#endif
NULL};
enum subsector_e {
@@ -175,10 +167,8 @@ enum ffloor_e {
ffloor_toplightlevel,
ffloor_bottomheight,
ffloor_bottompic,
-#ifdef ESLOPE
ffloor_tslope,
ffloor_bslope,
-#endif
ffloor_sector,
ffloor_flags,
ffloor_master,
@@ -195,10 +185,8 @@ static const char *const ffloor_opt[] = {
"toplightlevel",
"bottomheight",
"bottompic",
-#ifdef ESLOPE
"t_slope",
"b_slope",
-#endif
"sector", // secnum pushed as control sector userdata
"flags",
"master", // control linedef
@@ -208,7 +196,6 @@ static const char *const ffloor_opt[] = {
"alpha",
NULL};
-#ifdef ESLOPE
enum slope_e {
slope_valid = 0,
slope_o,
@@ -247,7 +234,6 @@ static const char *const vector_opt[] = {
"y",
"z",
NULL};
-#endif
static const char *const array_opt[] ={"iterate",NULL};
static const char *const valid_opt[] ={"valid",NULL};
@@ -463,14 +449,12 @@ static int sector_get(lua_State *L)
LUA_PushUserdata(L, sector->ffloors, META_FFLOOR);
lua_pushcclosure(L, sector_iterate, 2); // push lib_iterateFFloors and sector->ffloors as upvalues for the function
return 1;
-#ifdef ESLOPE
case sector_fslope: // f_slope
LUA_PushUserdata(L, sector->f_slope, META_SLOPE);
return 1;
case sector_cslope: // c_slope
LUA_PushUserdata(L, sector->c_slope, META_SLOPE);
return 1;
-#endif
}
return 0;
}
@@ -495,10 +479,8 @@ static int sector_set(lua_State *L)
case sector_heightsec: // heightsec
case sector_camsec: // camsec
case sector_ffloors: // ffloors
-#ifdef ESLOPE
case sector_fslope: // f_slope
case sector_cslope: // c_slope
-#endif
default:
return luaL_error(L, "sector_t field " LUA_QS " cannot be set.", sector_opt[field]);
case sector_floorheight: { // floorheight
@@ -1134,14 +1116,12 @@ static int ffloor_get(lua_State *L)
lua_pushlstring(L, levelflat->name, i);
return 1;
}
-#ifdef ESLOPE
case ffloor_tslope:
LUA_PushUserdata(L, *ffloor->t_slope, META_SLOPE);
return 1;
case ffloor_bslope:
LUA_PushUserdata(L, *ffloor->b_slope, META_SLOPE);
return 1;
-#endif
case ffloor_sector:
LUA_PushUserdata(L, §ors[ffloor->secnum], META_SECTOR);
return 1;
@@ -1183,10 +1163,8 @@ static int ffloor_set(lua_State *L)
switch(field)
{
case ffloor_valid: // valid
-#ifdef ESLOPE
case ffloor_tslope: // t_slope
case ffloor_bslope: // b_slope
-#endif
case ffloor_sector: // sector
case ffloor_master: // master
case ffloor_target: // target
@@ -1247,7 +1225,6 @@ static int ffloor_set(lua_State *L)
return 0;
}
-#ifdef ESLOPE
static int slope_get(lua_State *L)
{
pslope_t *slope = *((pslope_t **)luaL_checkudata(L, 1, META_SLOPE));
@@ -1422,7 +1399,6 @@ static int vector3_get(lua_State *L)
return 0;
}
-#endif
static int lib_getMapheaderinfo(lua_State *L)
{
@@ -1614,7 +1590,6 @@ int LUA_MapLib(lua_State *L)
lua_setfield(L, -2, "__newindex");
lua_pop(L, 1);
-#ifdef ESLOPE
luaL_newmetatable(L, META_SLOPE);
lua_pushcfunction(L, slope_get);
lua_setfield(L, -2, "__index");
@@ -1632,7 +1607,6 @@ int LUA_MapLib(lua_State *L)
lua_pushcfunction(L, vector3_get);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
-#endif
luaL_newmetatable(L, META_MAPHEADER);
lua_pushcfunction(L, mapheaderinfo_get);
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index dfb344e34..1361f8231 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -82,9 +82,7 @@ enum mobj_e {
mobj_extravalue2,
mobj_cusval,
mobj_cvmem,
-#ifdef ESLOPE
mobj_standingslope,
-#endif
mobj_colorized
};
@@ -145,9 +143,7 @@ static const char *const mobj_opt[] = {
"extravalue2",
"cusval",
"cvmem",
-#ifdef ESLOPE
"standingslope",
-#endif
"colorized",
NULL};
@@ -352,11 +348,9 @@ static int mobj_get(lua_State *L)
case mobj_cvmem:
lua_pushinteger(L, mo->cvmem);
break;
-#ifdef ESLOPE
case mobj_standingslope:
LUA_PushUserdata(L, mo->standingslope, META_SLOPE);
break;
-#endif
case mobj_colorized:
lua_pushboolean(L, mo->colorized);
break;
@@ -670,10 +664,8 @@ static int mobj_set(lua_State *L)
case mobj_cvmem:
mo->cvmem = luaL_checkinteger(L, 3);
break;
-#ifdef ESLOPE
case mobj_standingslope:
return NOSET;
-#endif
case mobj_colorized:
mo->colorized = luaL_checkboolean(L, 3);
break;
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index d9766513b..3aeeed734 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -17,6 +17,7 @@
#include "d_player.h"
#include "g_game.h"
#include "p_local.h"
+#include "d_clisrv.h"
#include "lua_script.h"
#include "lua_libs.h"
@@ -118,7 +119,7 @@ static int lib_iterateDisplayplayers(lua_State *L)
for (i++; i < MAXSPLITSCREENPLAYERS; i++)
{
- if (!playeringame[displayplayers[i]] || i > splitscreen)
+ if (i > splitscreen || !playeringame[displayplayers[i]])
return 0; // Stop! There are no more players for us to go through. There will never be a player gap in displayplayers.
if (!players[displayplayers[i]].mo)
@@ -139,6 +140,8 @@ static int lib_getDisplayplayers(lua_State *L)
lua_Integer i = luaL_checkinteger(L, 2);
if (i < 0 || i >= MAXSPLITSCREENPLAYERS)
return luaL_error(L, "displayplayers[] index %d out of range (0 - %d)", i, MAXSPLITSCREENPLAYERS-1);
+ if (i > splitscreen)
+ return 0;
if (!playeringame[displayplayers[i]])
return 0;
if (!players[displayplayers[i]].mo)
@@ -385,6 +388,8 @@ static int player_get(lua_State *L)
else if (fastcmp(field,"fovadd"))
lua_pushfixed(L, plr->fovadd);
#endif
+ else if (fastcmp(field,"ping"))
+ lua_pushinteger(L, playerpingtable[( plr - players )]);
else {
lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS);
I_Assert(lua_istable(L, -1));
diff --git a/src/lua_script.c b/src/lua_script.c
index 6961f2e97..7c951efb3 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -22,9 +22,7 @@
#include "byteptr.h"
#include "p_saveg.h"
#include "p_local.h"
-#ifdef ESLOPE
#include "p_slopes.h" // for P_SlopeById
-#endif
#ifdef LUA_ALLOW_BYTECODE
#include "d_netfil.h" // for LUA_DumpFile
#endif
@@ -472,9 +470,7 @@ enum
ARCH_SIDE,
ARCH_SUBSECTOR,
ARCH_SECTOR,
-#ifdef ESLOPE
ARCH_SLOPE,
-#endif
ARCH_MAPHEADER,
ARCH_TEND=0xFF,
@@ -494,9 +490,7 @@ static const struct {
{META_SIDE, ARCH_SIDE},
{META_SUBSECTOR,ARCH_SUBSECTOR},
{META_SECTOR, ARCH_SECTOR},
-#ifdef ESLOPE
{META_SLOPE, ARCH_SLOPE},
-#endif
{META_MAPHEADER, ARCH_MAPHEADER},
{NULL, ARCH_NULL}
};
@@ -701,7 +695,6 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
}
break;
}
-#ifdef ESLOPE
case ARCH_SLOPE:
{
pslope_t *slope = *((pslope_t **)lua_touserdata(gL, myindex));
@@ -713,7 +706,6 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
}
break;
}
-#endif
case ARCH_MAPHEADER:
{
mapheader_t *header = *((mapheader_t **)lua_touserdata(gL, myindex));
@@ -915,7 +907,6 @@ static UINT8 ArchiveValueDemo(int TABLESINDEX, int myindex)
}
break;
}
-#ifdef ESLOPE
case ARCH_SLOPE:
{
pslope_t *slope = *((pslope_t **)lua_touserdata(gL, myindex));
@@ -927,7 +918,6 @@ static UINT8 ArchiveValueDemo(int TABLESINDEX, int myindex)
}
break;
}
-#endif
case ARCH_MAPHEADER:
{
mapheader_t *header = *((mapheader_t **)lua_touserdata(gL, myindex));
@@ -1233,11 +1223,9 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
case ARCH_SECTOR:
LUA_PushUserdata(gL, §ors[READUINT16(save_p)], META_SECTOR);
break;
-#ifdef ESLOPE
case ARCH_SLOPE:
LUA_PushUserdata(gL, P_SlopeById(READUINT16(save_p)), META_SLOPE);
break;
-#endif
case ARCH_MAPHEADER:
LUA_PushUserdata(gL, mapheaderinfo[READUINT16(save_p)], META_MAPHEADER);
break;
@@ -1336,11 +1324,9 @@ static UINT8 UnArchiveValueDemo(int TABLESINDEX, char field[1024])
case ARCH_SECTOR:
LUA_PushUserdata(gL, §ors[READUINT16(demo_p)], META_SECTOR);
break;
-#ifdef ESLOPE
case ARCH_SLOPE:
LUA_PushUserdata(gL, P_SlopeById(READUINT16(demo_p)), META_SLOPE);
break;
-#endif
case ARCH_MAPHEADER:
LUA_PushUserdata(gL, mapheaderinfo[READUINT16(demo_p)], META_MAPHEADER);
break;
diff --git a/src/m_cheat.c b/src/m_cheat.c
index e7e877ada..a884ff7e4 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -862,13 +862,9 @@ static boolean OP_HeightOkay(player_t *player, UINT8 ceiling)
if (ceiling)
{
-#ifdef ESLOPE
// Truncate position to match where mapthing would be when spawned
// (this applies to every further P_GetZAt call as well)
fixed_t cheight = sec->c_slope ? P_GetZAt(sec->c_slope, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000) : sec->ceilingheight;
-#else
- fixed_t cheight = sec->ceilingheight;
-#endif
if (((cheight - player->mo->z - player->mo->height)>>FRACBITS) >= (1 << (16-ZSHIFT)))
{
@@ -879,11 +875,7 @@ static boolean OP_HeightOkay(player_t *player, UINT8 ceiling)
}
else
{
-#ifdef ESLOPE
fixed_t fheight = sec->f_slope ? P_GetZAt(sec->f_slope, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000) : sec->floorheight;
-#else
- fixed_t fheight = sec->floorheight;
-#endif
if (((player->mo->z - fheight)>>FRACBITS) >= (1 << (16-ZSHIFT)))
{
CONS_Printf(M_GetText("Sorry, you're too %s to place this object (max: %d %s).\n"), M_GetText("high"),
@@ -931,20 +923,12 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
mt->y = (INT16)(player->mo->y>>FRACBITS);
if (ceiling)
{
-#ifdef ESLOPE
fixed_t cheight = sec->c_slope ? P_GetZAt(sec->c_slope, mt->x << FRACBITS, mt->y << FRACBITS) : sec->ceilingheight;
-#else
- fixed_t cheight = sec->ceilingheight;
-#endif
mt->options = (UINT16)((cheight - player->mo->z - player->mo->height)>>FRACBITS);
}
else
{
-#ifdef ESLOPE
fixed_t fheight = sec->f_slope ? P_GetZAt(sec->f_slope, mt->x << FRACBITS, mt->y << FRACBITS) : sec->floorheight;
-#else
- fixed_t fheight = sec->floorheight;
-#endif
mt->options = (UINT16)((player->mo->z - fheight)>>FRACBITS);
}
mt->options <<= ZSHIFT;
@@ -1001,11 +985,7 @@ void OP_NightsObjectplace(player_t *player)
UINT16 angle = (UINT16)(player->anotherflyangle % 360);
INT16 temp = (INT16)FixedInt(AngleFixed(player->mo->angle)); // Traditional 2D Angle
sector_t *sec = player->mo->subsector->sector;
-#ifdef ESLOPE
fixed_t fheight = sec->f_slope ? P_GetZAt(sec->f_slope, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000) : sec->floorheight;
-#else
- fixed_t fheight = sec->floorheight;
-#endif
player->pflags |= PF_ATTACKDOWN;
@@ -1162,20 +1142,12 @@ void OP_ObjectplaceMovement(player_t *player)
if (!!(mobjinfo[op_currentthing].flags & MF_SPAWNCEILING) ^ !!(cv_opflags.value & MTF_OBJECTFLIP))
{
-#ifdef ESLOPE
fixed_t cheight = sec->c_slope ? P_GetZAt(sec->c_slope, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000) : sec->ceilingheight;
-#else
- fixed_t cheight = sec->ceilingheight;
-#endif
op_displayflags = (UINT16)((cheight - player->mo->z - mobjinfo[op_currentthing].height)>>FRACBITS);
}
else
{
-#ifdef ESLOPE
fixed_t fheight = sec->f_slope ? P_GetZAt(sec->f_slope, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000) : sec->floorheight;
-#else
- fixed_t fheight = sec->floorheight;
-#endif
op_displayflags = (UINT16)((player->mo->z - fheight)>>FRACBITS);
}
op_displayflags <<= ZSHIFT;
diff --git a/src/m_menu.c b/src/m_menu.c
index f04cf77f9..dc2363392 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -32,6 +32,7 @@
#include "sounds.h"
#include "s_sound.h"
#include "i_system.h"
+#include "i_threads.h"
// Addfile
#include "filesrch.h"
@@ -80,6 +81,11 @@ int snprintf(char *str, size_t n, const char *fmt, ...);
//int vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
#endif
+#ifdef HAVE_DISCORDRPC
+//#include "discord_rpc.h"
+#include "discord.h"
+#endif
+
#define SKULLXOFF -32
#define LINEHEIGHT 16
#define STRINGHEIGHT 8
@@ -118,6 +124,12 @@ typedef enum
NUM_QUITMESSAGES
} text_enum;
+#ifdef HAVE_THREADS
+I_mutex m_menu_mutex;
+#endif
+
+M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
+
const char *quitmsg[NUM_QUITMESSAGES];
// Stuff for customizing the player select screen Tails 09-22-2003
@@ -170,7 +182,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?
@@ -178,9 +189,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
@@ -245,7 +265,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
static void M_StartOfflineServerMenu(INT32 choice);
static void M_StartServer(INT32 choice);
@@ -286,6 +305,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
@@ -351,7 +373,6 @@ static void M_OGL_DrawColorMenu(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);
@@ -578,6 +599,10 @@ static menuitem_t MPauseMenu[] =
{IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16},
{IT_STRING | IT_CALL, NULL, "Switch Map..." , M_MapChange, 24},
+#ifdef HAVE_DISCORDRPC
+ {IT_STRING | IT_SUBMENU, NULL, "Ask To Join Requests...", &MISC_DiscordRequestsDef, 24},
+#endif
+
{IT_CALL | IT_STRING, NULL, "Continue", M_SelectableClearMenus, 40},
{IT_CALL | IT_STRING, NULL, "P1 Setup...", M_SetupMultiPlayer, 48}, // splitscreen
{IT_CALL | IT_STRING, NULL, "P2 Setup...", M_SetupMultiPlayer2, 56}, // splitscreen
@@ -601,6 +626,9 @@ typedef enum
mpause_addons = 0,
mpause_scramble,
mpause_switchmap,
+#ifdef HAVE_DISCORDRPC
+ mpause_discordrequests,
+#endif
mpause_continue,
mpause_psetupsplit,
@@ -651,6 +679,13 @@ typedef enum
spause_quit
} spause_e;
+#ifdef HAVE_DISCORDRPC
+static menuitem_t MISC_DiscordRequestsMenu[] =
+{
+ {IT_KEYHANDLER|IT_NOTHING, NULL, "", M_HandleDiscordRequests, 0},
+};
+#endif
+
// -----------------
// Misc menu options
// -----------------
@@ -982,7 +1017,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},
@@ -1012,54 +1047,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_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_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, 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
};
-
-static 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
// ------------------------------------
@@ -1336,11 +1347,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[] =
@@ -1389,7 +1406,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},
@@ -1402,6 +1419,19 @@ enum
op_addons_folder = 2,
};
+#ifdef HAVE_DISCORDRPC
+static menuitem_t OP_DiscordOptionsMenu[] =
+{
+ {IT_STRING | IT_CVAR, NULL, "Rich Presence", &cv_discordrp, 10},
+
+ {IT_HEADER, NULL, "Rich Presence Settings", NULL, 30},
+ {IT_STRING | IT_CVAR, NULL, "Streamer Mode", &cv_discordstreamer, 40},
+
+ {IT_STRING | IT_CVAR, NULL, "Allow Ask To Join", &cv_discordasks, 60},
+ {IT_STRING | IT_CVAR, NULL, "Allow Invites", &cv_discordinvites, 70},
+};
+#endif
+
static menuitem_t OP_HUDOptionsMenu[] =
{
{IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", &cv_showhud, 10},
@@ -1642,6 +1672,19 @@ menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72);
menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72);
menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72);
+#ifdef HAVE_DISCORDRPC
+menu_t MISC_DiscordRequestsDef = {
+ NULL,
+ sizeof (MISC_DiscordRequestsMenu)/sizeof (menuitem_t),
+ &MPauseDef,
+ MISC_DiscordRequestsMenu,
+ M_DrawDiscordRequests,
+ 0, 0,
+ 0,
+ NULL
+};
+#endif
+
// Misc Main Menu
menu_t MISC_ScrambleTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ScrambleTeamMenu, &MPauseDef, 27, 40);
menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ChangeTeamMenu, &MPauseDef, 27, 40);
@@ -1925,17 +1968,6 @@ menu_t MP_ConnectDef =
0,
M_CancelConnect
};
-menu_t MP_RoomDef =
-{
- "M_MULTI",
- sizeof (MP_RoomMenu)/sizeof (menuitem_t),
- &MP_ConnectDef,
- MP_RoomMenu,
- M_DrawRoomMenu,
- 27, 32,
- 0,
- NULL
-};
#endif
menu_t MP_PlayerSetupDef =
{
@@ -2068,6 +2100,9 @@ menu_t OP_OpenGLColorDef =
menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30);
menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
+#ifdef HAVE_DISCORDRPC
+menu_t OP_DiscordOptionsDef = DEFAULTMENUSTYLE(NULL, OP_DiscordOptionsMenu, &OP_DataOptionsDef, 30, 30);
+#endif
menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 30, 30);
// ==========================================================================
@@ -2929,7 +2964,6 @@ boolean M_Responder(event_t *ev)
//make sure the game doesn't still think we're in a netgame.
if (!Playing() && netgame && multiplayer)
{
- MSCloseUDPSocket(); // Clean up so we can re-open the connection later.
netgame = false;
multiplayer = false;
}
@@ -3221,12 +3255,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)))
@@ -3298,6 +3338,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;
}
@@ -3394,6 +3447,19 @@ void M_Ticker(void)
if (--vidm_testingmode == 0)
setmodeneeded = vidm_previousmode + 1;
}
+
+#if defined (MASTERSERVER) && defined (HAVE_THREADS)
+ I_lock_mutex(&ms_ServerList_mutex);
+ {
+ if (ms_ServerList)
+ {
+ CL_QueryServerList(ms_ServerList);
+ free(ms_ServerList);
+ ms_ServerList = NULL;
+ }
+ }
+ I_unlock_mutex(ms_ServerList_mutex);
+#endif
}
//
@@ -4088,6 +4154,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();
}
@@ -4866,7 +4951,7 @@ static boolean M_AddonsRefresh(void)
else if (majormods && !prevmajormods)
{
S_StartSound(NULL, sfx_s221);
- message = va("%c%s\x80\nGameplay has now been modified.\nIf you wish to play Record Attack mode, restart the game to clear existing addons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
+ message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
prevmajormods = majormods;
}
@@ -6222,7 +6307,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
@@ -6263,6 +6353,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
// ======
@@ -7461,7 +7565,7 @@ static void M_DrawStatsMaps(int location)
else
V_DrawString(20, y, 0, va("%s %s %s",
mapheaderinfo[mnum]->lvlttl,
- (mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "ZONE"),
+ (mapheaderinfo[mnum]->zonttl[0] ? mapheaderinfo[mnum]->zonttl : "Zone"),
mapheaderinfo[mnum]->actnum));
y += 8;
@@ -8287,7 +8391,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)
@@ -8359,34 +8574,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)
-{
- const char *rmotd;
-
- // use generic drawer for cursor, items and title
- M_DrawGenericMenu();
-
- V_DrawString(currentMenu->x - 16, currentMenu->y, highlightflags, M_GetText("Select a room"));
-
- 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);
+#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)
@@ -8395,6 +8594,7 @@ static void M_DrawConnectMenu(void)
const char *gt = "Unknown";
const char *spd = "";
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;
@@ -8402,20 +8602,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) ? "